{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# FIT5202 Assignment 2A : Building Models for eCommerce Fraud Detection\n",
    "\n",
    "## Table of Contents\n",
    "*  \n",
    "    * [Part 1 : Data Loading, Transformation and Exploration](#part-1)\n",
    "    * [Part 2 : Feature extraction and ML training](#part-2)\n",
    "    * [Part 3 : Customer Segmentation and Knowledge sharing with K-Mean](#part-3)\n",
    "    * [Part 4 : Data Ethics, Privacy, and Security](#part-4)\n",
    " \n",
    "Please add code/markdown cells if needed."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Part 1: Data Loading, Transformation and Exploration <a class=\"anchor\" name=\"part-1\"></a>\n",
    "## 1.1 Data Loading\n",
    "In this section, you must load the given datasets into PySpark DataFrames and use DataFrame functions to process the data. Spark SQL usage is discouraged, and you can only use pandas to format results. For plotting, various visualisation packages can be used, but please ensure that you have included instructions to install the additional packages and that the installation will be successful in the provided docker container (in case your marker needs to clear the notebook and rerun it).\n",
    "\n",
    "### 1.1.1 Data Loading <a class=\"anchor\" name=\"1.1\"></a>\n",
    "1.1.1 Write the code to create a SparkSession. For creating the SparkSession, you need to use a SparkConf object to configure the Spark app with a proper application name, to ensure the maximum partition size does not exceed 16MB, and to run locally with all CPU cores on your machine (note: if you have insufficient RAM, reducing the number of cores is acceptable.)  (2%)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyspark.sql import SparkSession \n",
    "from pyspark import SparkContext\n",
    "# conf = \"spark.sql.files.maxPartitionBytes\", \"16777216\"\n",
    "#recommand config : \n",
    "#spark = SparkSession.builder.master(\"local[*]\").appName(\"eCommerce Fraud Detection\")\n",
    "#.config(\"spark.driver.memory\", \"8g\").config(\"spark.executor.memory\", \"8g\").config(\"spark.executor.cores\", \"4\").getOrCreate()\n",
    "\n",
    "appName = \"eCommerce Fraud detecion\"\n",
    "spark = SparkSession.builder.master('local').appName(appName).getOrCreate()\n",
    "\n",
    "sc = spark.sparkContext\n",
    "\n",
    "\n",
    "sc.setLogLevel('error')\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1.1.2 Write code to define the schemas for the category, customer, product, browsing behaviour and transaction datasets, following the data types suggested in the metadata file. (3%)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyspark.sql.types import *\n",
    "category_schema = StructType([StructField('category_id', IntegerType() , True),\n",
    "                             StructField('cat_level1' ,StringType(), True),\n",
    "                             StructField('cat_level2',StringType(),True),\n",
    "                             StructField('cat_level3',StringType(),True)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyspark.sql.types import *\n",
    "\n",
    "customer_schema = StructType([\n",
    "    StructField(\"customer_id\", IntegerType(), True),\n",
    "    StructField(\"first_name\", StringType(), True),\n",
    "    StructField(\"last_name\", StringType(), True),\n",
    "    StructField(\"username\", StringType(), True),\n",
    "    StructField(\"email\", StringType(), True),\n",
    "    StructField(\"gender\", StringType(), True),\n",
    "    StructField(\"birthdate\", TimestampType(), True),  # You may want to convert it to a date later\n",
    "    StructField(\"first_join_date\", TimestampType(), True)\n",
    "])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "product_schema = StructType([\n",
    "    StructField(\"id\", IntegerType(), True),\n",
    "    StructField(\"gender\", StringType(), True),\n",
    "    StructField(\"baseColour\", StringType(), True),\n",
    "    StructField(\"season\", StringType(), True),\n",
    "    StructField(\"year\", IntegerType(), True),\n",
    "    StructField(\"usage\", StringType(), True),\n",
    "    StructField(\"productDisplayName\", StringType(), True),\n",
    "    StructField(\"category_id\", IntegerType(), True)\n",
    "])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyspark.sql.types import TimestampType\n",
    "\n",
    "browsing_behaviour_schema = StructType([\n",
    "    StructField(\"session_id\", StringType(), True),\n",
    "    StructField(\"event_type\", StringType(), True),\n",
    "    StructField(\"event_time\", TimestampType(), True),  # Time is stored as Timestamp\n",
    "    StructField(\"traffic_source\", StringType(), True),\n",
    "    StructField(\"device_type\", StringType(), True)\n",
    "])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "transaction_schema = StructType([\n",
    "    StructField(\"created_at\", TimestampType(), True),\n",
    "    StructField(\"customer_id\", IntegerType(), True),\n",
    "    StructField(\"transaction_id\", StringType(), True),\n",
    "    StructField(\"session_id\", StringType(), True),\n",
    "    StructField(\"product_metadata\", StringType(), True),  # Likely needs to be parsed later\n",
    "    StructField(\"payment_method\", StringType(), True),\n",
    "    StructField(\"payment_status\", StringType(), True),\n",
    "    StructField(\"promo_amount\", FloatType(), True),\n",
    "    StructField(\"promo_code\", StringType(), True),\n",
    "    StructField(\"shipment_fee\", FloatType(), True),\n",
    "    StructField(\"shipment_location_lat\", StringType(), True),\n",
    "    StructField(\"shipment_location_long\", StringType(), True),\n",
    "    StructField(\"total_amount\", FloatType(), True),\n",
    "    StructField(\"clear_payment\", StringType(), True)\n",
    "])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "fraud_transaction_schema = StructType([\n",
    "    StructField(\"transaction_id\", StringType(), True),\n",
    "    StructField(\"is_fraud\", BooleanType(), True)  \n",
    "])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "customer_session_schema = StructType([\n",
    "    StructField(\"session_id\", StringType(), True),\n",
    "     StructField(\"customer_id\", IntegerType(), True)\n",
    "    \n",
    "])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1.1.3 Using predefined schemas, write code to load the CSV files into separate data frames. Print the schemas of all data frames. (2%)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "root\n",
      " |-- customer_id: integer (nullable = true)\n",
      " |-- first_name: string (nullable = true)\n",
      " |-- last_name: string (nullable = true)\n",
      " |-- username: string (nullable = true)\n",
      " |-- email: string (nullable = true)\n",
      " |-- gender: string (nullable = true)\n",
      " |-- birthdate: timestamp (nullable = true)\n",
      " |-- first_join_date: timestamp (nullable = true)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "customer_df = spark.read.schema(customer_schema).csv(\"customer.csv\", header=True)\n",
    "\n",
    "# Show the schema and data\n",
    "customer_df.printSchema()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+-----------+----------+-----------+--------------------+--------------------+------+-------------------+-------------------+\n",
      "|customer_id|first_name|  last_name|            username|               email|gender|          birthdate|    first_join_date|\n",
      "+-----------+----------+-----------+--------------------+--------------------+------+-------------------+-------------------+\n",
      "|       2870|      Lala|    Maryati|671a0865-ac4e-4dc...|671a0865_ac4e_4dc...|     F|1996-06-14 00:00:00|2019-07-21 00:00:00|\n",
      "|       8193|  Maimunah| Laksmiwati|83be2ba7-8133-48a...|83be2ba7_8133_48a...|     F|1993-08-16 00:00:00|2017-07-16 00:00:00|\n",
      "|       7279|   Bakiman|Simanjuntak|3250e5a3-1d23-467...|3250e5a3_1d23_467...|     M|1989-01-23 00:00:00|2020-08-23 00:00:00|\n",
      "|      88813|   Cahyadi|  Maheswara|df797edf-b465-4a8...|df797edf_b465_4a8...|     M|1991-01-05 00:00:00|2021-10-03 00:00:00|\n",
      "|      82542|   Irnanto|     Wijaya|36ab08e1-03de-42a...|36ab08e1_03de_42a...|     M|2000-07-15 00:00:00|2021-04-11 00:00:00|\n",
      "+-----------+----------+-----------+--------------------+--------------------+------+-------------------+-------------------+\n",
      "only showing top 5 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "customer_df.show(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "root\n",
      " |-- category_id: integer (nullable = true)\n",
      " |-- cat_level1: string (nullable = true)\n",
      " |-- cat_level2: string (nullable = true)\n",
      " |-- cat_level3: string (nullable = true)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "category_df = spark.read.schema(category_schema).csv(\"category.csv\", header=True)\n",
    "\n",
    "# Show the schema and data\n",
    "category_df.printSchema()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "root\n",
      " |-- id: integer (nullable = true)\n",
      " |-- gender: string (nullable = true)\n",
      " |-- baseColour: string (nullable = true)\n",
      " |-- season: string (nullable = true)\n",
      " |-- year: integer (nullable = true)\n",
      " |-- usage: string (nullable = true)\n",
      " |-- productDisplayName: string (nullable = true)\n",
      " |-- category_id: integer (nullable = true)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "product_df = spark.read.schema(product_schema).csv(\"product.csv\",header = True)\n",
    "# show the schema and data\n",
    "product_df.printSchema()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "root\n",
      " |-- created_at: timestamp (nullable = true)\n",
      " |-- customer_id: integer (nullable = true)\n",
      " |-- transaction_id: string (nullable = true)\n",
      " |-- session_id: string (nullable = true)\n",
      " |-- product_metadata: string (nullable = true)\n",
      " |-- payment_method: string (nullable = true)\n",
      " |-- payment_status: string (nullable = true)\n",
      " |-- promo_amount: float (nullable = true)\n",
      " |-- promo_code: string (nullable = true)\n",
      " |-- shipment_fee: float (nullable = true)\n",
      " |-- shipment_location_lat: string (nullable = true)\n",
      " |-- shipment_location_long: string (nullable = true)\n",
      " |-- total_amount: float (nullable = true)\n",
      " |-- clear_payment: string (nullable = true)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "transaction_df = spark.read.schema(transaction_schema).csv(\"transactions.csv\",header = True)\n",
    "\n",
    "transaction_df.printSchema()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+--------------------+-----------+--------------------+--------------------+--------------------+--------------+--------------+------------+-------------+------------+---------------------+----------------------+------------+-------------+\n",
      "|          created_at|customer_id|      transaction_id|          session_id|    product_metadata|payment_method|payment_status|promo_amount|   promo_code|shipment_fee|shipment_location_lat|shipment_location_long|total_amount|clear_payment|\n",
      "+--------------------+-----------+--------------------+--------------------+--------------------+--------------+--------------+------------+-------------+------------+---------------------+----------------------+------------+-------------+\n",
      "|2021-03-24 09:16:...|      14159|511f59f8-3ef5-438...|4d55839d-4b3e-406...|[{'product_id': 4...|   Credit Card|       Success|      4735.0|WEEKENDMANTAP|     10000.0|    -4.26351275671241|      105.489401701251|    271777.0|            1|\n",
      "|2022-07-18 17:54:...|      22576|8e509f58-7f8d-421...|4d5839f4-313f-45c...|[{'product_id': 2...|         Gopay|       Success|         0.0|         NULL|     10000.0|    -7.91707661186231|       110.13187555325|    323489.0|            1|\n",
      "|2023-08-05 16:17:...|      18696|29d32f23-a07a-4f2...|4d5ad667-a524-4de...|[{'product_id': 2...|         Gopay|       Success|         0.0|         NULL|     50000.0|    -7.39661418330981|      109.511262594032|    305028.0|            1|\n",
      "|2020-08-16 08:46:...|      90136|a3e90650-4db3-408...|4d5e5a02-dfed-40e...|[{'product_id': 2...|   Credit Card|       Success|         0.0|         NULL|     10000.0|   -0.637290541399757|      109.492521253314|    853787.0|            1|\n",
      "|2022-06-06 00:25:...|      18960|12ffdc68-a0ba-44e...|000128d0-c51c-411...|[{'product_id': 5...|    Debit Card|       Success|         0.0|         NULL|      5000.0|    -7.32004136393024|      111.225797135699|    276002.0|            1|\n",
      "+--------------------+-----------+--------------------+--------------------+--------------------+--------------+--------------+------------+-------------+------------+---------------------+----------------------+------------+-------------+\n",
      "only showing top 5 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "transaction_df.show(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "root\n",
      " |-- session_id: string (nullable = true)\n",
      " |-- event_type: string (nullable = true)\n",
      " |-- event_time: timestamp (nullable = true)\n",
      " |-- traffic_source: string (nullable = true)\n",
      " |-- device_type: string (nullable = true)\n",
      "\n",
      "+------------------------------------+----------+-----------------------+--------------+-----------+\n",
      "|session_id                          |event_type|event_time             |traffic_source|device_type|\n",
      "+------------------------------------+----------+-----------------------+--------------+-----------+\n",
      "|c9718135-8134-42b2-8e1e-2737fd6b49b1|AP        |2020-12-17 07:04:52.331|MOBILE        |Android    |\n",
      "|c9718135-8134-42b2-8e1e-2737fd6b49b1|CL        |2020-12-06 09:07:47.331|MOBILE        |Android    |\n",
      "|c9718135-8134-42b2-8e1e-2737fd6b49b1|SER       |2020-12-17 07:12:08.331|MOBILE        |Android    |\n",
      "|c9718135-8134-42b2-8e1e-2737fd6b49b1|CL        |2021-01-08 02:28:32.331|MOBILE        |Android    |\n",
      "|c9718135-8134-42b2-8e1e-2737fd6b49b1|ATC       |2021-01-19 00:17:38.331|MOBILE        |Android    |\n",
      "+------------------------------------+----------+-----------------------+--------------+-----------+\n",
      "only showing top 5 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "browsing_behaviour_df = spark.read.schema(browsing_behaviour_schema).csv(\"browsing_behaviour.csv\",header = True)\n",
    "\n",
    "browsing_behaviour_df.printSchema()\n",
    "\n",
    "browsing_behaviour_df.show(5 , truncate =False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "root\n",
      " |-- transaction_id: string (nullable = true)\n",
      " |-- is_fraud: boolean (nullable = true)\n",
      "\n",
      "+------------------------------------+--------+\n",
      "|transaction_id                      |is_fraud|\n",
      "+------------------------------------+--------+\n",
      "|9e81cadd-3f76-4bba-a03a-6a1ad8e69472|true    |\n",
      "|204532ae-b268-4d2e-8b1f-0b7230801662|true    |\n",
      "|880767b5-a94c-4fbc-b85c-d77e25450e60|true    |\n",
      "|780aa6e8-0671-4fb2-b0e4-dd7655b4a5af|true    |\n",
      "|737f279a-bb52-4f32-96d7-58035ac7f249|true    |\n",
      "+------------------------------------+--------+\n",
      "only showing top 5 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "fraud_transaction_df = spark.read.schema(fraud_transaction_schema).csv(\"fraud_transaction.csv\" , header = True)\n",
    "\n",
    "fraud_transaction_df.printSchema()\n",
    "\n",
    "fraud_transaction_df.show(5 , truncate =False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "root\n",
      " |-- session_id: string (nullable = true)\n",
      " |-- customer_id: integer (nullable = true)\n",
      "\n",
      "+------------------------------------+-----------+\n",
      "|session_id                          |customer_id|\n",
      "+------------------------------------+-----------+\n",
      "|3abaa6ce-e320-4e51-9469-d9f3fa328e86|5868       |\n",
      "|2ee5ead1-f13e-4759-92df-7ff48475e970|4774       |\n",
      "|93325fb6-eb00-4268-bb0e-6471795a0ad0|4774       |\n",
      "|bcad5a61-1b67-448d-8ff4-781d67bc56e4|4774       |\n",
      "|df1042ab-13e6-4072-b9d2-64a81974c51a|4774       |\n",
      "+------------------------------------+-----------+\n",
      "only showing top 5 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "customer_session_df = spark.read.schema(customer_session_schema).csv(\"customer_session.csv\" , header = True)\n",
    "customer_session_df.printSchema()\n",
    "\n",
    "customer_session_df.show(5 , truncate =False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.2 Data Transformation to Create Features <a class=\"anchor\" name=\"1.2\"></a>\n",
    "In the browsing behaviour dataset, there are 10 types of events:  \n",
    "VC(Viewing Category), VI(Viewing Item), VP(Viewing Promotion), AP(Add Promotion), CL(Click on a product/category) , ATC(Add a product to Shopping Cart), CO(CheckOut), HP(View HomePage), SCR(Mouse Scrolling), SER(Search for a product/category)  \n",
    "We categorise them into three different levels:  \n",
    "L1(actions that are highly likely lead to a purchase): AP, ATC, CO  \n",
    "L2(actions may lead to purchase): VC, VP, VI, SER  \n",
    "L3(not very important - just browsing):  SCR, HP, CL  \n",
    "Perform the following tasks based on the loaded data frames and create a new data frame.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1.2.1 For each transaction (linked to a browsing session), count the number of actions in each level and create 3 columns(L1_count, L2_count, L3_count)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyspark.sql import functions as F\n",
    "\n",
    "# Create columns for L1, L2, and L3 counts based on event types\n",
    "agg_browsing_df = transaction_df.select('transaction_id','customer_id','session_id').join(browsing_behaviour_df,'session_id','left').groupBy(\"transaction_id\",\"session_id\").agg(\n",
    "    F.sum(F.when(F.col(\"event_type\").isin([\"AP\", \"ATC\", \"CO\"]), 1).otherwise(0)).alias(\"L1_count\"),\n",
    "    F.sum(F.when(F.col(\"event_type\").isin([\"VC\", \"VP\", \"VI\", \"SER\"]), 1).otherwise(0)).alias(\"L2_count\"),\n",
    "    F.sum(F.when(F.col(\"event_type\").isin([\"SCR\", \"HP\", \"CL\"]), 1).otherwise(0)).alias(\"L3_count\")\n",
    ")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+------------------------------------+------------------------------------+--------+--------+--------+\n",
      "|transaction_id                      |session_id                          |L1_count|L2_count|L3_count|\n",
      "+------------------------------------+------------------------------------+--------+--------+--------+\n",
      "|63385654-5ba0-4c42-9b0e-6c7423491ddf|00008e68-a4d4-4b5d-ab7a-9fbd44f7b7fd|2       |0       |2       |\n",
      "|515ceebe-c5a2-4ec7-9426-ba07447b1172|0000a158-a793-4f64-ba52-660a24ff8af6|3       |1       |4       |\n",
      "|12ffdc68-a0ba-44e6-92e5-51173a6c91b1|000128d0-c51c-411b-88e6-0cf5d9642d2d|3       |6       |6       |\n",
      "|7063ddb3-a798-4327-9744-dd4c49dfbd48|000130b9-07d1-4374-99b8-89d769c46c9f|2       |11      |34      |\n",
      "|ab618799-1999-4939-83b9-848c25a60b73|00013ac1-f66e-492c-ac04-4e0f1ecf2ff8|2       |14      |12      |\n",
      "+------------------------------------+------------------------------------+--------+--------+--------+\n",
      "only showing top 5 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "agg_browsing_df.select('transaction_id',\"session_id\",\"L1_count\",\"L2_count\",\"L3_count\").show(5 , truncate = False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1.2.2 Create two columns with a percentage ratio of L1 and L2 actions. (i.e. L1 ratio = L1/(L1+L2+L3) * 100%)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Calculate total actions and percentage ratios for L1 and L2\n",
    "agg_browsing_df = agg_browsing_df.withColumn(\n",
    "    \"total_actions\", F.col(\"L1_count\") + F.col(\"L2_count\") + F.col(\"L3_count\")\n",
    ")\n",
    "\n",
    "level_df_ratio = agg_browsing_df.withColumn(\n",
    "    \"L1_ratio\", F.round((F.col(\"L1_count\") / F.col(\"total_actions\")) * 100 , 2)\n",
    ").withColumn(\n",
    "    \"L2_ratio\", F.round((F.col(\"L2_count\") / F.col(\"total_actions\")) * 100 ,2)\n",
    ")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+--------------------+--------------------+--------+--------+--------+-------------+--------+--------+\n",
      "|      transaction_id|          session_id|L1_count|L2_count|L3_count|total_actions|L1_ratio|L2_ratio|\n",
      "+--------------------+--------------------+--------+--------+--------+-------------+--------+--------+\n",
      "|63385654-5ba0-4c4...|00008e68-a4d4-4b5...|       2|       0|       2|            4|    50.0|     0.0|\n",
      "|515ceebe-c5a2-4ec...|0000a158-a793-4f6...|       3|       1|       4|            8|    37.5|    12.5|\n",
      "|12ffdc68-a0ba-44e...|000128d0-c51c-411...|       3|       6|       6|           15|    20.0|    40.0|\n",
      "|7063ddb3-a798-432...|000130b9-07d1-437...|       2|      11|      34|           47|    4.26|    23.4|\n",
      "|ab618799-1999-493...|00013ac1-f66e-492...|       2|      14|      12|           28|    7.14|    50.0|\n",
      "+--------------------+--------------------+--------+--------+--------+-------------+--------+--------+\n",
      "only showing top 5 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "level_df_ratio.show(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "level_df_ratio = level_df_ratio.select('transaction_id','session_id','L1_count','L2_count','L3_count','L1_ratio','L2_ratio')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1.2.3 For each unique browsing session, based on event_time, extract the time of day as 4 groups: morning(6am-11:59am), afternoon(12pm-5:59pm), evening(6pm-11:59pm), night(12am-5:59am), add a column. (note: use medium time if a browsing session spans across different groups. For example, if a session starts at 10 am and ends at 1 pm, use 11:30 => (10+13)/2)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+--------------------+----+-----+---+------------------+----------+\n",
      "|          session_id|year|month|day|          avg_hour|time_group|\n",
      "+--------------------+----+-----+---+------------------+----------+\n",
      "|b746ff42-f05a-4a8...|2023|    4| 19|              18.0|   evening|\n",
      "|b48b2400-6209-461...|2021|    9|  4|               4.0|     night|\n",
      "|1e7c394e-a17d-4f3...|2022|   12| 16|              16.5| afternoon|\n",
      "|99534a14-dac1-45a...|2021|   10| 13|               0.0|     night|\n",
      "|737b6e9e-d84b-412...|2021|   11|  9|               7.0|   morning|\n",
      "|b42a1872-ab70-4e5...|2022|    7|  5|13.333333333333334| afternoon|\n",
      "|1fec6b3d-ecca-462...|2020|    6| 29|               0.0|     night|\n",
      "|75e8caed-a8f8-481...|2022|    1| 31|              16.0| afternoon|\n",
      "|a741abff-2152-410...|2022|    6|  1|              10.0|   morning|\n",
      "|caf556e0-f443-45c...|2020|    3| 31|              14.0| afternoon|\n",
      "|aeafeb87-c18e-4fb...|2023|   11| 13|              15.0| afternoon|\n",
      "|d4ec3722-a16b-43e...|2022|   12| 25|              20.0|   evening|\n",
      "|de262429-7240-4d6...|2020|    5|  4|              14.0| afternoon|\n",
      "|23336eb8-9bb0-4ca...|2023|    5|  8|               5.0|     night|\n",
      "|1a858533-3ba0-41d...|2023|    7| 28|              10.0|   morning|\n",
      "|2a29731b-62e0-4ca...|2020|    8| 10|              23.0|   evening|\n",
      "|d3c9f7eb-474a-475...|2023|    5| 17|              17.0| afternoon|\n",
      "|15024096-fe32-4c9...|2023|    1| 27|              20.0|   evening|\n",
      "|2dfb6037-3df7-403...|2022|    2| 14|               3.0|     night|\n",
      "|421b6216-b3ed-481...|2022|    1|  2|11.666666666666666|   morning|\n",
      "+--------------------+----+-----+---+------------------+----------+\n",
      "only showing top 20 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from pyspark.sql.functions import year, month, dayofmonth, hour, avg, col, udf\n",
    "from pyspark.sql.types import StringType\n",
    "\n",
    "# Assuming `browsing_behaviour_df` is your input DataFrame\n",
    "session_time_df = browsing_behaviour_df.select('session_id', 'event_time')\n",
    "\n",
    "# Add year, month, day, and hour columns\n",
    "session_time_df = session_time_df.withColumn('year', year(col('event_time'))) \\\n",
    "                                 .withColumn('month', month(col('event_time'))) \\\n",
    "                                 .withColumn('day', dayofmonth(col('event_time'))) \\\n",
    "                                 .withColumn('hour', hour(col('event_time')))\n",
    "\n",
    "# Correct the extract_time_group logic\n",
    "def extract_time_group(hour):\n",
    "    if 6 <= hour < 12:\n",
    "        return \"morning\"\n",
    "    elif 12 <= hour < 18:\n",
    "        return \"afternoon\"\n",
    "    elif 18 <= hour < 24:\n",
    "        return \"evening\"\n",
    "    else:\n",
    "        return \"night\"\n",
    "\n",
    "# Register the UDF\n",
    "extract_time_group_udf = udf(extract_time_group, StringType())\n",
    "\n",
    "# Group by session_id, year, day, and compute the average hour\n",
    "session_time_df = session_time_df.groupBy('session_id', 'year','month', 'day').agg(avg('hour').alias('avg_hour'))\n",
    "\n",
    "# Apply the UDF to categorize based on the average hour\n",
    "session_time_df = session_time_df.withColumn('time_group', extract_time_group_udf(col('avg_hour')))\n",
    "\n",
    "# Show the result\n",
    "session_time_df.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+--------------------+----+-----+---+--------+----------+\n",
      "|          session_id|year|month|day|avg_hour|time_group|\n",
      "+--------------------+----+-----+---+--------+----------+\n",
      "|b746ff42-f05a-4a8...|2023|    4| 19|    18.0|   evening|\n",
      "|b48b2400-6209-461...|2021|    9|  4|     4.0|     night|\n",
      "|1e7c394e-a17d-4f3...|2022|   12| 16|    16.5| afternoon|\n",
      "|99534a14-dac1-45a...|2021|   10| 13|     0.0|     night|\n",
      "|737b6e9e-d84b-412...|2021|   11|  9|     7.0|   morning|\n",
      "+--------------------+----+-----+---+--------+----------+\n",
      "only showing top 5 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "session_time_df.show(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "session_time_df = session_time_df.select('session_id','time_group')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1.2.4 Join data frames to find customer information and add columns to feature_df: gender, age, geolocation, first join year. (note: For some columns, you need to perform transformations. For age, keep the integer only by rounding.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyspark.sql.functions import year, col, current_date, datediff, round\n",
    "\n",
    "#calculate age for customer\n",
    "customer_df_age = customer_df.withColumn('age' , year(current_date())- year(col('birthdate')))\n",
    "#extract join year\n",
    "customer_df_age = customer_df_age.withColumn('first_join_year' , year('first_join_date'))\n",
    "#join table\n",
    "joined_table = transaction_df.join(customer_df_age , 'customer_id')\n",
    "\n",
    "feature_df = joined_table.select('customer_id','age','gender','shipment_location_lat','shipment_location_long',\n",
    "                                 'first_join_year','session_id','transaction_id')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+-----------+----------+-----------+--------------------+--------------------+------+-------------------+-------------------+---+---------------+\n",
      "|customer_id|first_name|  last_name|            username|               email|gender|          birthdate|    first_join_date|age|first_join_year|\n",
      "+-----------+----------+-----------+--------------------+--------------------+------+-------------------+-------------------+---+---------------+\n",
      "|       2870|      Lala|    Maryati|671a0865-ac4e-4dc...|671a0865_ac4e_4dc...|     F|1996-06-14 00:00:00|2019-07-21 00:00:00| 28|           2019|\n",
      "|       8193|  Maimunah| Laksmiwati|83be2ba7-8133-48a...|83be2ba7_8133_48a...|     F|1993-08-16 00:00:00|2017-07-16 00:00:00| 31|           2017|\n",
      "|       7279|   Bakiman|Simanjuntak|3250e5a3-1d23-467...|3250e5a3_1d23_467...|     M|1989-01-23 00:00:00|2020-08-23 00:00:00| 35|           2020|\n",
      "|      88813|   Cahyadi|  Maheswara|df797edf-b465-4a8...|df797edf_b465_4a8...|     M|1991-01-05 00:00:00|2021-10-03 00:00:00| 33|           2021|\n",
      "|      82542|   Irnanto|     Wijaya|36ab08e1-03de-42a...|36ab08e1_03de_42a...|     M|2000-07-15 00:00:00|2021-04-11 00:00:00| 24|           2021|\n",
      "+-----------+----------+-----------+--------------------+--------------------+------+-------------------+-------------------+---+---------------+\n",
      "only showing top 5 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "customer_df_age.show(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+-----------+---+------+---------------------+----------------------+---------------+--------------------+--------------------+\n",
      "|customer_id|age|gender|shipment_location_lat|shipment_location_long|first_join_year|          session_id|      transaction_id|\n",
      "+-----------+---+------+---------------------+----------------------+---------------+--------------------+--------------------+\n",
      "|      14159| 31|     F|    -4.26351275671241|      105.489401701251|           2019|4d55839d-4b3e-406...|511f59f8-3ef5-438...|\n",
      "|      22576| 32|     F|    -7.91707661186231|       110.13187555325|           2020|4d5839f4-313f-45c...|8e509f58-7f8d-421...|\n",
      "|      18696| 28|     F|    -7.39661418330981|      109.511262594032|           2020|4d5ad667-a524-4de...|29d32f23-a07a-4f2...|\n",
      "|      90136| 24|     F|   -0.637290541399757|      109.492521253314|           2017|4d5e5a02-dfed-40e...|a3e90650-4db3-408...|\n",
      "|      18960| 24|     F|    -7.32004136393024|      111.225797135699|           2018|000128d0-c51c-411...|12ffdc68-a0ba-44e...|\n",
      "+-----------+---+------+---------------------+----------------------+---------------+--------------------+--------------------+\n",
      "only showing top 5 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "feature_df.show(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "657989"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    " feature_df.count()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Perform the left join on 'transaction_id' and drop the unnecessary columns\n",
    "feature_df = feature_df.alias('feature') \\\n",
    "    .join(level_df_ratio.alias('level'), 'transaction_id', 'left') \\\n",
    "    .drop('level.customer_id').drop('level.session_id') "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['transaction_id', 'customer_id', 'age', 'gender', 'shipment_location_lat', 'shipment_location_long', 'first_join_year', 'session_id', 'session_id', 'L1_count', 'L2_count', 'L3_count', 'L1_ratio', 'L2_ratio']\n"
     ]
    }
   ],
   "source": [
    "print(feature_df.columns)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "feature_df = feature_df.alias('feature').join(session_time_df.alias('time'),'session_id','left').drop('time.session_id')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['session_id', 'transaction_id', 'customer_id', 'age', 'gender', 'shipment_location_lat', 'shipment_location_long', 'first_join_year', 'session_id', 'L1_count', 'L2_count', 'L3_count', 'L1_ratio', 'L2_ratio', 'time_group']\n"
     ]
    }
   ],
   "source": [
    "print(feature_df.columns)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3491425"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "feature_df.count()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyspark.sql import functions as F"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1.2.5 Join data frames to find out the number of purchases the customer has made, add a column."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyspark.sql.functions import count\n",
    "purchases_df = feature_df.groupBy(\"customer_id\").agg(count(\"transaction_id\").alias(\"purchase_count\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+-----------+--------------+\n",
      "|customer_id|purchase_count|\n",
      "+-----------+--------------+\n",
      "|        496|           440|\n",
      "|      26706|            88|\n",
      "|      87120|           545|\n",
      "|      76493|           160|\n",
      "|      18800|            34|\n",
      "+-----------+--------------+\n",
      "only showing top 5 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "purchases_df.show(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyspark.sql.functions import broadcast \n",
    "purchases_df_b = broadcast(purchases_df)\n",
    "\n",
    "feature_df = feature_df.join(purchases_df_b,'customer_id' , 'left')\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3491425"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "feature_df.count()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1.2.6 Attach the transaction labels for fraud/non-fraud."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+--------------------+-----------+\n",
      "|      transaction_id|fraud_label|\n",
      "+--------------------+-----------+\n",
      "|511f59f8-3ef5-438...|  Non-Fraud|\n",
      "|8e509f58-7f8d-421...|  Non-Fraud|\n",
      "|29d32f23-a07a-4f2...|  Non-Fraud|\n",
      "|a3e90650-4db3-408...|  Non-Fraud|\n",
      "|12ffdc68-a0ba-44e...|  Non-Fraud|\n",
      "|430dc90b-6c04-408...|  Non-Fraud|\n",
      "|89a39293-dee2-48a...|  Non-Fraud|\n",
      "|cddea303-22d6-41f...|  Non-Fraud|\n",
      "|9cf8b365-0b86-4c3...|  Non-Fraud|\n",
      "|6323379e-5dee-4d0...|  Non-Fraud|\n",
      "+--------------------+-----------+\n",
      "only showing top 10 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from pyspark.sql import functions as F\n",
    "\n",
    "# Perform a left join on transaction_id to retain all transactions from transactions_df\n",
    "transactions_labeled_df = transaction_df.join(\n",
    "    fraud_transaction_df,\n",
    "    on=\"transaction_id\",  # Join on the transaction_id column\n",
    "    how=\"left\"  # Left join to keep all transactions from transactions_df\n",
    ")\n",
    "\n",
    "# Add a new column fraud_label based on the is_fraud column\n",
    "transactions_labeled_df = transactions_labeled_df.withColumn(\n",
    "    \"fraud_label\",\n",
    "    F.when(F.col(\"is_fraud\") == True, \"Fraud\")  # If is_fraud is True, label as \"Fraud\"\n",
    "     .otherwise(\"Non-Fraud\")  # Otherwise, label as \"Non-Fraud\"\n",
    ")\n",
    "\n",
    "# Optionally drop the is_fraud column if you no longer need it\n",
    "transactions_labeled_df = transactions_labeled_df.drop(\"is_fraud\")\n",
    "\n",
    "# Show the labeled transactions\n",
    "transactions_labeled_df.select(\"transaction_id\", \"fraud_label\").show(10)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [],
   "source": [
    "transactions_labeled_df = transactions_labeled_df.select(\"transaction_id\", \"fraud_label\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "feature_df = feature_df.join(transactions_labeled_df,'transaction_id')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3442640"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "feature_df.filter(F.col('fraud_label')=='Non-Fraud').count()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "48785"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "feature_df.filter(F.col('fraud_label')=='Fraud').count()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+--------------------+-----------+--------------------+---+------+---------------------+----------------------+---------------+--------------------+--------+--------+--------+--------+--------+----------+--------------+-----------+\n",
      "|      transaction_id|customer_id|          session_id|age|gender|shipment_location_lat|shipment_location_long|first_join_year|          session_id|L1_count|L2_count|L3_count|L1_ratio|L2_ratio|time_group|purchase_count|fraud_label|\n",
      "+--------------------+-----------+--------------------+---+------+---------------------+----------------------+---------------+--------------------+--------+--------+--------+--------+--------+----------+--------------+-----------+\n",
      "|0000121c-49bc-45a...|      31256|254b3804-255c-40e...| 24|     F|    -6.28159211549684|      106.879595256967|           2020|254b3804-255c-40e...|       2|       0|       2|    50.0|     0.0|   morning|           265|  Non-Fraud|\n",
      "|000039e2-b270-4a6...|      17423|c22bedf5-cfe0-467...| 33|     M|     1.16853007015238|      113.081552086415|           2017|c22bedf5-cfe0-467...|       4|       3|       3|    40.0|    30.0|   evening|           317|  Non-Fraud|\n",
      "|000039e2-b270-4a6...|      17423|c22bedf5-cfe0-467...| 33|     M|     1.16853007015238|      113.081552086415|           2017|c22bedf5-cfe0-467...|       4|       3|       3|    40.0|    30.0|   morning|           317|  Non-Fraud|\n",
      "|000039e2-b270-4a6...|      17423|c22bedf5-cfe0-467...| 33|     M|     1.16853007015238|      113.081552086415|           2017|c22bedf5-cfe0-467...|       4|       3|       3|    40.0|    30.0| afternoon|           317|  Non-Fraud|\n",
      "|000039e2-b270-4a6...|      17423|c22bedf5-cfe0-467...| 33|     M|     1.16853007015238|      113.081552086415|           2017|c22bedf5-cfe0-467...|       4|       3|       3|    40.0|    30.0| afternoon|           317|  Non-Fraud|\n",
      "+--------------------+-----------+--------------------+---+------+---------------------+----------------------+---------------+--------------------+--------+--------+--------+--------+--------+----------+--------------+-----------+\n",
      "only showing top 5 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "feature_df.show(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.3 Exploring the Data <a class=\"anchor\" name=\"1.3\"></a>\n",
    "**1.3.1 With the feature_df, write code to show the basic statistics: a) For each numeric column, show count, mean, stddev, min, max, 25 percentile, 50 percentile, 75 percentile; b) For each non-numeric column, display the top-5 values and the corresponding counts; c) For each boolean column, display the value and count. (3%)**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 159,
   "metadata": {},
   "outputs": [],
   "source": [
    "non_numeric_column = ['customer_id','gender','shipment_location_long','shipment_location_lat','first_join_year',\n",
    "                     'transaction_id','time_group']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 160,
   "metadata": {},
   "outputs": [],
   "source": [
    "numeric_column = ['age','purchase_count','L1_count','L2_count','L3_count','L1_ratio','L2_ratio']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 161,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyspark.sql.functions import col \n",
    "\n",
    "# Step 1: Remove duplicates\n",
    "all_feature_df = feature_df.dropDuplicates()\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 162,
   "metadata": {},
   "outputs": [],
   "source": [
    "all_feature_df = all_feature_df.cache()  # cache DataFrame\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 163,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "root\n",
      " |-- age: integer (nullable = true)\n",
      " |-- purchase_count: long (nullable = true)\n",
      " |-- L1_count: long (nullable = true)\n",
      " |-- L2_count: long (nullable = true)\n",
      " |-- L3_count: long (nullable = true)\n",
      " |-- L1_ratio: double (nullable = true)\n",
      " |-- L2_ratio: double (nullable = true)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "all_feature_df.select(numeric_column).printSchema()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 164,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Step 1: Take a sample (10% of the data)\n",
    "sampled_df = all_feature_df.sample(fraction=0.1)  # Adjust the fraction (0.1) as per your requirement"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 165,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+-------+-----------------+-----------------+------------------+-----------------+-----------------+------------------+------------------+\n",
      "|summary|              age|   purchase_count|          L1_count|         L2_count|         L3_count|          L1_ratio|          L2_ratio|\n",
      "+-------+-----------------+-----------------+------------------+-----------------+-----------------+------------------+------------------+\n",
      "|  count|           163399|           163399|            163399|           163399|           163399|            163399|            163399|\n",
      "|   mean|28.36740738927411|260.8823248612293|3.7737501453497266|3.700542842979455|8.473491269836412| 33.59572580003451|19.385021144560888|\n",
      "| stddev|7.276717522836029|193.2024556578309| 2.354131050904223|4.571939631018823|10.25990146550903|18.570403383155423|12.727393163580832|\n",
      "|    min|                8|                1|                 2|                0|                1|              0.54|               0.0|\n",
      "|    25%|               23|              107|                 2|                1|                3|             18.18|             11.11|\n",
      "|    50%|               28|              220|                 3|                2|                5|             33.33|              20.0|\n",
      "|    75%|               33|              380|                 4|                5|               10|              50.0|             28.57|\n",
      "|    max|               69|             1257|                50|              107|              408|             96.15|             68.75|\n",
      "+-------+-----------------+-----------------+------------------+-----------------+-----------------+------------------+------------------+\n",
      "\n",
      "Column: age\n",
      "25th percentile: 23.0\n",
      "50th percentile (Median): 28.0\n",
      "75th percentile: 33.0\n",
      "----------------------------------------\n",
      "Column: purchase_count\n",
      "25th percentile: 104.0\n",
      "50th percentile (Median): 218.0\n",
      "75th percentile: 376.0\n",
      "----------------------------------------\n",
      "Column: L1_count\n",
      "25th percentile: 2.0\n",
      "50th percentile (Median): 3.0\n",
      "75th percentile: 4.0\n",
      "----------------------------------------\n",
      "Column: L2_count\n",
      "25th percentile: 1.0\n",
      "50th percentile (Median): 2.0\n",
      "75th percentile: 5.0\n",
      "----------------------------------------\n",
      "Column: L3_count\n",
      "25th percentile: 3.0\n",
      "50th percentile (Median): 5.0\n",
      "75th percentile: 10.0\n",
      "----------------------------------------\n",
      "Column: L1_ratio\n",
      "25th percentile: 18.18\n",
      "50th percentile (Median): 33.33\n",
      "75th percentile: 47.37\n",
      "----------------------------------------\n",
      "Column: L2_ratio\n",
      "25th percentile: 11.11\n",
      "50th percentile (Median): 20.0\n",
      "75th percentile: 28.57\n",
      "----------------------------------------\n"
     ]
    }
   ],
   "source": [
    "\n",
    "\n",
    "# Step 2: Calculate common statistics (count, mean, stddev, min, max) on the sampled data\n",
    "summary_stats_sampled = sampled_df.select(numeric_column).summary()\n",
    "summary_stats_sampled.show()\n",
    "\n",
    "# Step 3: Calculate percentiles (25th, 50th, 75th) on the sampled data\n",
    "percentiles = [0.25, 0.5, 0.75]\n",
    "\n",
    "# Dictionary to store the results\n",
    "percentile_results_sampled = {}\n",
    "\n",
    "for column in numeric_column:\n",
    "    # Compute the 25th, 50th, and 75th percentiles\n",
    "    approx_quantiles = sampled_df.stat.approxQuantile(column, percentiles, 0.01)\n",
    "    percentile_results_sampled[column] = approx_quantiles\n",
    "\n",
    "# Display the percentile results for the sampled data\n",
    "for column, values in percentile_results_sampled.items():\n",
    "    print(f\"Column: {column}\")\n",
    "    print(f\"25th percentile: {values[0]}\")\n",
    "    print(f\"50th percentile (Median): {values[1]}\")\n",
    "    print(f\"75th percentile: {values[2]}\")\n",
    "    print(\"-\" * 40)\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 166,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Top-5 values for column customer_id\n",
      "+-----------+-----+\n",
      "|customer_id|count|\n",
      "+-----------+-----+\n",
      "|      43202| 1016|\n",
      "|      29496|  964|\n",
      "|      82237|  825|\n",
      "|      10167|  807|\n",
      "|      69740|  741|\n",
      "+-----------+-----+\n",
      "only showing top 5 rows\n",
      "\n",
      "Top-5 values for column gender\n",
      "+------+-------+\n",
      "|gender|  count|\n",
      "+------+-------+\n",
      "|     F|1034456|\n",
      "|     M| 595645|\n",
      "+------+-------+\n",
      "\n",
      "Top-5 values for column shipment_location_long\n",
      "+----------------------+-----+\n",
      "|shipment_location_long|count|\n",
      "+----------------------+-----+\n",
      "|      106.712704190821|  415|\n",
      "|      108.241545754375|  415|\n",
      "|      106.897045431069|  326|\n",
      "|      106.875296511436|  311|\n",
      "|      106.884148212605|  295|\n",
      "+----------------------+-----+\n",
      "only showing top 5 rows\n",
      "\n",
      "Top-5 values for column shipment_location_lat\n",
      "+---------------------+-----+\n",
      "|shipment_location_lat|count|\n",
      "+---------------------+-----+\n",
      "|    -6.88804707780373|  415|\n",
      "|    -6.15913128097598|  415|\n",
      "|    -6.16995833528818|  326|\n",
      "|    -6.27086782576512|  311|\n",
      "|    -6.27655875548687|  295|\n",
      "+---------------------+-----+\n",
      "only showing top 5 rows\n",
      "\n",
      "Top-5 values for column first_join_year\n",
      "+---------------+------+\n",
      "|first_join_year| count|\n",
      "+---------------+------+\n",
      "|           2017|418062|\n",
      "|           2018|374212|\n",
      "|           2019|295560|\n",
      "|           2020|249609|\n",
      "|           2016|172789|\n",
      "+---------------+------+\n",
      "only showing top 5 rows\n",
      "\n",
      "Top-5 values for column transaction_id\n",
      "+--------------------+-----+\n",
      "|      transaction_id|count|\n",
      "+--------------------+-----+\n",
      "|000039e2-b270-4a6...|    4|\n",
      "|002d3e38-2b6c-409...|    4|\n",
      "|00011b0e-9b48-4a8...|    4|\n",
      "|00464ebc-bda9-417...|    4|\n",
      "|0000ddae-de8f-4bc...|    4|\n",
      "+--------------------+-----+\n",
      "only showing top 5 rows\n",
      "\n",
      "Top-5 values for column time_group\n",
      "+----------+------+\n",
      "|time_group| count|\n",
      "+----------+------+\n",
      "|   morning|479178|\n",
      "| afternoon|432174|\n",
      "|   evening|362195|\n",
      "|     night|356552|\n",
      "|      NULL|     2|\n",
      "+----------+------+\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Step 3: Top-5 values and counts for non-numeric columns\n",
    "for col in non_numeric_column:\n",
    "    print(f\"Top-5 values for column {col}\")\n",
    "    all_feature_df.groupBy(col).count().orderBy('count', ascending=False).show(5)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**1.3.2 Explore the dataframe and write code to present two plots worthy of presentation to the company, describe your plots and discuss the findings from the plots. (8%)**\n",
    "One of the plots needs to be based on feature_df in regard to fraudulent behaviour; you’re free to choose the other one.  \n",
    "Hint 1: You can use basic plots (e.g., histograms, line charts, scatter plots) to show the relationship between a column and the label or more advanced plots like correlation plots.  \n",
    "Hint 2: If your data is too large for plotting, consider using sampling before plotting.  \n",
    "150 words max for each plot’s description and discussion  \n",
    "Feel free to use any plotting libraries: matplotlib, seabon, plotly, etc.  \n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 169,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "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yGDhwoW0a9eO6Oho7r333grb/PnnnxkyZAi9e/fmvffew2AwsH79eu69917y8/PNCUNN6PV6Bg0axPr160lISLhq1h0VFcWBAwd49dVXad++PVlZWRw4cID09HQA3nnnHSZNmsSZM2f4+uuvK93GW2+9Rfv27Xn99ddxdnYmKCjomrHdeeedjB49mkceeYRjx47xwgsvcPz4cfbs2VOtZohhw4Yxf/58nn/+ed5++226d+8OXL0mSlEURo0axU8//cSsWbO4+eab+f3333nppZfYtWsXu3btsqjxOXz4MNOnT+e5557D29ubjz76iAkTJtCuXTtuueWWSvdRXpV+ZfW5r68vqamp9O3bl6KiIl555RVat27Nd999x4wZMzhz5gzvvPPODR1XRVEsPpgB1Go1anXZ75nExETGjh3Ls88+y/z5883LT506xe233860adNwcHDgxIkTLFy4kL1797J169Zr7rMyBQUFDBo0iAsXLrBgwQLat2/Pxo0bK30P3IikpCRuuukm1Go1L774Im3btmXXrl3MmzePc+fO8cknn1iUf/vtt+nQoYO5v98LL7zA7bffztmzZzEYDLzwwgvk5eXx3//+16I55FrNsmfOnCE8PJyHH34Yg8HAuXPnWLp0Kf/4xz84cuRIhfP57rvv5t5772XChAkcOXKEWbNmAX93GSg/R3fu3MmLL75Ir169+O233xg6dGi1j8/ChQvp1q0bixcv5uWXX75quQULFvD8889z//33s2DBAtLT05kzZw7h4eHs27fP4rwrLi5m5MiRTJgwgenTp/PLL7/wyiuvYDAYePHFF6sUl5yn9X+eVsU333zDr7/+yosvvoiPjw9eXl5AWYI3efJkAgICANi9ezdPPPEEf/31V4XXvCqfmU8//TRr1qxh3rx5dOvWjby8PI4ePWr+zinfZ3BwMPfddx9ubm4kJiby7rvv0qtXL44fP46HhwdQlkT16tWL4uJinn/+eTp37kx6ejo//PADmZmZdO/enU8++YR//etf/Pvf/2bYsGEA16yFevTRR/nggw94/PHHGT58OOfOneOFF15g27ZtHDhwwLxvKHttH3jgAaZPn85LL73E119/zaxZs/Dz8+PBBx8EYNGiRcyZM4d///vf3HLLLRQXF3PixIkKP96vSWkiPv30UwVQ3nvvPUVRFOXixYuKo6OjcvPN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",
      "text/plain": [
       "<Figure size 600x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "\n",
    "# Sample the data if it's too large\n",
    "sampled_df = feature_df.sample(fraction=0.1)\n",
    "\n",
    "# Plot a histogram showing the age distribution by fraud label\n",
    "plt.figure(figsize=(6, 4))\n",
    "sns.histplot(data=sampled_df.toPandas(), x=\"age\", hue=\"fraud_label\", kde=True, bins=30)\n",
    "plt.title(\"Age Distribution for Fraudulent and Non-Fraudulent Transactions\")\n",
    "plt.xlabel(\"Age\")\n",
    "plt.ylabel(\"Count\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This histogram displays the age distribution for fraudulent and non-fraudulent transactions. The x-axis represents customer age, while the y-axis represents the count of transactions. The majority of transactions, both fraudulent and non-fraudulent, are performed by customers between the ages of 20 and 40, with a peak around the age of 30. The blue area shows non-fraudulent transactions, which dominate the distribution, while the orange area represents fraudulent transactions.\n",
    "\n",
    "The fraudulent transactions are barely visible, indicating that fraud occurs much less frequently than non-fraud transactions across all age groups. While the non-fraudulent transactions follow a normal distribution, the fraudulent ones are minimal across all ages, suggesting that fraud might not be significantly correlated with customer age.\n",
    "\n",
    "This visualization highlights that fraud is rare and evenly spread across the age spectrum, with no specific age group being heavily targeted for fraudulent activity."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 177,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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TWRkZCWmQURERFQxXoknFBMRERGVFYsbIiIi0iosboiIiEirsLghIiIircLihoiIiLQKixsiIiLSKixuiIiISKuwuCEiIiKtwuKGiIiItAqLGyIiItIqLG6IiIhIq7C4ISIiIq3C4oaIiIi0CosbIiIi0iosboiIiEirsLghIiIircLihoiIiLQKixsiIiLSKixuiIiISKuwuCEiIiKtwuKGiIiItAqLGyIiItIqLG6IiIhIq7C4ISIiIq3C4oaIiIi0CosbIiIi0iosboiIiEirsLghIiIircLihoiIiLQKixsiIiLSKixuiIiISKuwuCEiIiKtwuKGiIiItAqLGyIiItIqLG6IiIhIq7C4ISIiIq3C4oaIiIi0SrUubmbNmoU33ngDZmZmsLW1Rc+ePXH+/HlZjBACYWFhcHR0hJGREXx8fHD27FlZTE5ODkaNGgUbGxuYmJige/fuuHbtmiwmMzMTAQEBUKlUUKlUCAgIwN27d2Uxqamp6NatG0xMTGBjY4PRo0cjNze3UvadiIiIyqdaFzcHDhzAiBEjEBcXh927dyM/Px8dO3ZEdna2FDN37lxERERgyZIlOH78OOzt7eHr64t79+5JMWPHjsXmzZuxceNGxMbG4v79++jatSsKCgqkGH9/fyQkJCA6OhrR0dFISEhAQECANL+goABdunRBdnY2YmNjsXHjRmzatAmhoaEv52AQERFRmehVdQKliY6Olk2vWrUKtra2iI+PR9u2bSGEwKJFizB16lT07t0bABAVFQU7OzusX78ew4YNg1qtxsqVK7FmzRp06NABALB27Vo4OTlhz5498PPzQ3JyMqKjoxEXF4dWrVoBAFasWAFPT0+cP38eLi4uiImJQVJSEq5evQpHR0cAwIIFCxAYGIivvvoK5ubmL/HIEBER0bNU656bp6nVagCAlZUVACAlJQXp6eno2LGjFKNUKuHt7Y3Dhw8DAOLj45GXlyeLcXR0hLu7uxRz5MgRqFQqqbABgNatW0OlUsli3N3dpcIGAPz8/JCTk4P4+Phn5pyTk4OsrCzZi4iIiCrPK1PcCCEQEhKCt956C+7u7gCA9PR0AICdnZ0s1s7OTpqXnp4OAwMDWFpalhpja2tbbJu2traymKe3Y2lpCQMDAymmJLNmzZLu41GpVHByctJkt4mIiEhDr0xxM3LkSPz111/YsGFDsXkKhUI2LYQo1va0p2NKii9PzNMmT54MtVotva5evVpqXkRERPRiXoniZtSoUdi6dSv27duH1157TWq3t7cHgGI9JxkZGVIvi729PXJzc5GZmVlqzI0bN4pt9+bNm7KYp7eTmZmJvLy8Yj06T1IqlTA3N5e9iIiIqPJU6+JGCIGRI0fi119/xR9//AFnZ2fZfGdnZ9jb22P37t1SW25uLg4cOAAvLy8AQIsWLaCvry+LSUtLQ2JiohTj6ekJtVqNY8eOSTFHjx6FWq2WxSQmJiItLU2KiYmJgVKpRIsWLSp+54mIiKhcqvVoqREjRmD9+vX47bffYGZmJvWcqFQqGBkZQaFQYOzYsQgPD0f9+vVRv359hIeHw9jYGP7+/lLskCFDEBoaCmtra1hZWWH8+PHw8PCQRk+5urqiU6dOCAoKwnfffQcAGDp0KLp27QoXFxcAQMeOHeHm5oaAgADMmzcPd+7cwfjx4xEUFMTeGCIiomqkWhc333zzDQDAx8dH1r5q1SoEBgYCACZMmICHDx8iODgYmZmZaNWqFWJiYmBmZibFL1y4EHp6eujbty8ePnyI9u3bIzIyErq6ulLMunXrMHr0aGlUVffu3bFkyRJpvq6uLnbs2IHg4GC0adMGRkZG8Pf3x/z58ytp74mIiKg8qnVxI4R4boxCoUBYWBjCwsKeGWNoaIjFixdj8eLFz4yxsrLC2rVrS91WrVq1sH379ufmRERERFWnWt9zQ0RERKQpFjdERESkVVjcEBERkVZhcUNERERahcUNERERaRUWN0RERKRVWNwQERGRVmFxQ0RERFqFxQ0RERFpFRY3REREpFVY3BAREZFWYXFDREREWoXFDREREWkVFjdERESkVVjcEBERkVZhcUNERERahcUNERERaRUWN0RERKRVWNwQERGRVmFxQ0RERFqFxQ0RERFpFRY3REREpFVY3BAREZFWYXFDREREWoXFDREREWkVFjdERESkVVjcEBERkVZhcUNERERahcUNERERaRUWN0RERKRVWNwQERGRVmFxQ0RERFqFxQ0RERFpFRY3REREpFVY3BAREZFWYXFDREREWoXFDREREWkVFjdERESkVfSqOoFX0bJlyzBv3jykpaWhUaNGWLRoEd5+++2qTqvaSZ3hUdUplEutz89UdQrVEs8nEb0qWNxo6Mcff8TYsWOxbNky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",
      "text/plain": [
       "<Figure size 600x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Plot a histogram showing time group distribution for fraudulent vs. non-fraudulent events\n",
    "plt.figure(figsize=(6, 4))\n",
    "sns.countplot(data=sampled_df.toPandas(), x=\"time_group\", hue=\"fraud_label\")\n",
    "plt.title(\"Event Time Group Distribution by Fraud Label\")\n",
    "plt.xlabel(\"Time Group\")\n",
    "plt.ylabel(\"Number of Events\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This bar chart shows the distribution of events across different time groups (morning, afternoon, evening, night) for both fraudulent and non-fraudulent transactions. The x-axis represents the time groups, while the y-axis indicates the number of events.\n",
    "\n",
    "The blue bars represent non-fraudulent transactions, which are significantly more frequent across all time groups, with the highest number of events occurring in the morning, followed by the afternoon. Night and evening show fewer events but still a notable volume of non-fraudulent transactions.\n",
    "\n",
    "The orange bars represent fraudulent transactions, which are comparatively much smaller in volume, showing that fraud happens infrequently regardless of the time group. Fraudulent activity is relatively consistent across all time groups but remains significantly lower than non-fraudulent transactions.\n",
    "\n",
    "This graph suggests that most user activity, whether fraudulent or non-fraudulent, occurs during the morning and afternoon, with no specific time group showing a significant spike in fraudulent behavior."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Part 2. Feature extraction and ML training <a class=\"anchor\" name=\"part-2\"></a>\n",
    "In this section, you must use PySpark DataFrame functions and ML packages for data preparation, model building, and evaluation. Other ML packages, such as scikit-learn, would receive zero marks.\n",
    "### 2.1 Discuss the feature selection and prepare the feature columns\n",
    "\n",
    "2.1.1 Based on the data exploration from 1.2 and considering the use case, discuss the importance of those features (For example, which features may be useless and should be removed, which feature has a significant impact on the label column, which should be transformed), which features you are planning to use? Discuss the reasons for selecting them and how you create/transform them\n",
    "300 words max for the discussion\n",
    "Please only use the provided data for model building\n",
    "You can create/add additional features based on the dataset\n",
    "Hint - Use the insights from the data exploration/domain knowledge/statistical models to consider whether to create more feature columns, whether to remove some columns"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Key Features for Model Building:\n",
    "\n",
    "- **Transaction Amount (`amt`)**: This feature is crucial as fraudulent transactions are often skewed towards smaller amounts, as seen from the histogram analysis. We plan to add this feature and consider adding transformations, such as binning transaction amounts into ranges (e.g., small, medium, large) to capture trends better.\n",
    "  \n",
    "- **perfer buying time (`perfer_buying_time`)**: perfer buying time is a new created features in the feature data frame to replace time_group.It show customer buying behaviour by frequency of the time group.\n",
    "\n",
    "- **Customer Behavior Features (`L1_count`, `L2_count`, `L3_count`)**: These features represent customer interaction counts. Fraudsters may show a distinct interaction pattern, so these features are important. We can explore whether normalizing these features would help models better capture fraud patterns.\n",
    "\n",
    "- **Location Information (`shipment_location_long`, `shipment_location_lat`)**: These features could potentially indicate fraud if a transaction occurs far from typical locations for a customer. However, raw latitude and longitude are often not directly useful.we may transfer it to the count of geolocation of each customer , to detect the location change.\n",
    "\n",
    "### Features to Remove or Transform:\n",
    "\n",
    "- **Gender**: Based on domain knowledge and initial analysis, gender is unlikely to significantly influence fraudulent behavior. This feature could be removed unless domain knowledge suggests otherwise.\n",
    "  \n",
    "- **Transaction ID and Customer ID**: These are unique identifiers and provide no predictive power, so they should be removed from the model.\n",
    "\n",
    "    \n",
    "\n",
    "\n",
    "\n",
    "### Feature Engineering:\n",
    "\n",
    "- **Purchase Frequency (`purchases`)**: This feature can be normalized by the number of days since the first transaction (`first_join_year`) to create a \"purchase frequency\" feature. Frequent purchases within a short period could be an indicator of fraudulent activity.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2.1.2 Write code to create/transform the columns based on your discussion above\n",
    "Hint: You can use one data frame for both use cases (classification and k-mean later in part 3) since you can select your desired columns as the input and output for each use case. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Combined sampled dataframe size: 70394\n",
      "Fraud count in sampled data: 1040\n",
      "Non-Fraud count in sampled data: 69354\n"
     ]
    }
   ],
   "source": [
    "from pyspark.sql.functions import col\n",
    "\n",
    "#sample data for calculation\n",
    "# Separate the 'Non-Fraud' and 'Fraud' data\n",
    "non_fraud_data = feature_df.filter(col(\"fraud_label\") == 'Non-Fraud')\n",
    "fraud_data = feature_df.filter(col(\"fraud_label\") == 'Fraud')\n",
    "\n",
    "# Sample 20% of the 'Non-Fraud' and 'Fraud' data separately\n",
    "sampled_non_fraud = non_fraud_data.sample(fraction=0.02, seed=42)\n",
    "sampled_fraud = fraud_data.sample(fraction=0.02, seed=42)\n",
    "\n",
    "# Combine the sampled 'Non-Fraud' and 'Fraud' data together\n",
    "sampled_combined = sampled_non_fraud.union(sampled_fraud)\n",
    "\n",
    "# Show the size of the combined sampled dataframe\n",
    "print(f\"Combined sampled dataframe size: {sampled_combined.count()}\")\n",
    "\n",
    "# Optionally, check the count of Fraud and Non-Fraud cases in the combined sample\n",
    "print(f\"Fraud count in sampled data: {sampled_combined.filter(col('fraud_label') == 'Fraud').count()}\")\n",
    "print(f\"Non-Fraud count in sampled data: {sampled_combined.filter(col('fraud_label') == 'Non-Fraud').count()}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "root\n",
      " |-- customer_id: integer (nullable = true)\n",
      " |-- distinct_geolocations: long (nullable = false)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "import pyspark.sql.functions as F\n",
    "\n",
    "# Count distinct geolocation pairs (lat and long) for each customer\n",
    "geolocation_distinct_count = sampled_combined.groupBy(\"customer_id\") \\\n",
    "    .agg(F.countDistinct(\"shipment_location_lat\", \"shipment_location_long\").alias(\"distinct_geolocations\"))\n",
    "\n",
    "geolocation_distinct_count.printSchema()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "\n",
    "final_element_df = sampled_combined.join(geolocation_distinct_count,'customer_id')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "DataFrame[customer_id: int, transaction_id: string, session_id: string, age: int, gender: string, first_join_year: int, session_id: string, L1_count: bigint, L2_count: bigint, L3_count: bigint, L1_ratio: double, L2_ratio: double, time_group: string, purchase_count: bigint, fraud_label: string, distinct_geolocations: bigint]"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "final_element_df.drop(\"shipment_location_lat\", \"shipment_location_long\").distinct()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "\n",
    "from pyspark.sql import functions as F\n",
    "behavior_cols = [\"L1_count\", \"L2_count\", \"L3_count\"]\n",
    "for col_name in behavior_cols:\n",
    "    max_val =final_element_df.agg({col_name: \"max\"}).collect()[0][0]\n",
    "    final_element_df = final_element_df.withColumn(f\"{col_name}_normalized\", F.col(col_name) / max_val)\n",
    "\n",
    "\n",
    "\n",
    "#  Create a \"purchase frequency\" feature based on purchases and first join year\n",
    "current_year = 2024  # For example purposes\n",
    "final_element_df = final_element_df.withColumn(\n",
    "    \"purchase_frequency\",\n",
    "    F.col(\"purchase_count\") / (current_year - F.col(\"first_join_year\"))\n",
    ")\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "#drop the column we transfered\n",
    "final_element_df = final_element_df.drop(\"L1_count\", \"L2_count\", \"L3_count\",\"first_join_year\").distinct()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### perfer buying time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Group by customer_id and time_of_day, and count the number of transactions in each time group\n",
    "time_preference_df = feature_df.groupBy(\"customer_id\", \"time_group\").agg(\n",
    "    F.count(\"transaction_id\").alias(\"time_group_count\")\n",
    ")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find the preferred buying time for each customer (time group with the maximum count)\n",
    "from pyspark.sql.window import Window\n",
    "\n",
    "# Define a window specification to partition data by customer_id and order by time_group_count\n",
    "window_spec = Window.partitionBy(\"customer_id\").orderBy(F.desc(\"time_group_count\"))\n",
    "\n",
    "# Use row_number to rank time groups for each customer and keep the highest ranked (most frequent) time group\n",
    "time_preference_df = time_preference_df.withColumn(\n",
    "    \"row_num\", F.row_number().over(window_spec)\n",
    ").filter(F.col(\"row_num\") == 1).drop(\"row_num\")\n",
    "\n",
    "# Rename the column to 'prefer_buying_time'\n",
    "time_preference_df = time_preference_df.withColumnRenamed(\"time_group\", \"prefer_buying_time\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Join the preferred buying time back with the main features_df\n",
    "final_element_df = final_element_df.join(time_preference_df.select(\"customer_id\", \"prefer_buying_time\"), \"customer_id\", \"left\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+-----------+------------------+--------------------+\n",
      "|customer_id|prefer_buying_time|      transaction_id|\n",
      "+-----------+------------------+--------------------+\n",
      "|       6397|           morning|0334a580-4a68-4f1...|\n",
      "|      76110|           morning|0848b8c2-660f-4ac...|\n",
      "|       6397|           morning|097a5230-0f18-439...|\n",
      "|      24347|           morning|0a977921-ce7c-48e...|\n",
      "|       6397|           morning|106df71f-d198-41b...|\n",
      "|      76493|         afternoon|10c5a0cb-1c0e-49c...|\n",
      "|      29744|         afternoon|1143f799-fc43-4f3...|\n",
      "|      41409|           morning|16d4b551-73a4-4b8...|\n",
      "|       1829|           morning|171f1d86-e9ae-418...|\n",
      "|      57201|           morning|18d1dbd3-c639-49c...|\n",
      "|      81734|         afternoon|1b409828-0992-420...|\n",
      "|      87120|           morning|1cda38bf-8767-4ac...|\n",
      "|      28088|           morning|1ce64d02-88c9-45c...|\n",
      "|      29894|         afternoon|1d36a5ca-984e-41d...|\n",
      "|       1088|           morning|200d0ba2-20fc-47b...|\n",
      "|      41409|           morning|228dfadf-48c9-4fd...|\n",
      "|      41409|           morning|228dfadf-48c9-4fd...|\n",
      "|      28088|           morning|24394efc-2e53-4f7...|\n",
      "|       6397|           morning|24488773-de10-4ef...|\n",
      "|      87120|           morning|27df2110-987d-413...|\n",
      "+-----------+------------------+--------------------+\n",
      "only showing top 20 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "final_element_df.select(\"customer_id\", \"prefer_buying_time\", \"transaction_id\").show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [],
   "source": [
    "final_element_df = final_element_df.drop('customer_id','gender','session_id').distinct()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### amount category"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [],
   "source": [
    "amt = transaction_df.select('total_amount')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+-------+-----------------+\n",
      "|summary|     total_amount|\n",
      "+-------+-----------------+\n",
      "|  count|           657989|\n",
      "|   mean|550212.4390711699|\n",
      "| stddev| 817215.951585763|\n",
      "|    min|          10898.0|\n",
      "|    25%|         203616.0|\n",
      "|    50%|         302665.0|\n",
      "|    75%|         514922.0|\n",
      "|    max|      2.3504488E7|\n",
      "+-------+-----------------+\n",
      "\n"
     ]
    }
   ],
   "source": [
    "amt.summary().show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "from pyspark.sql import functions as F\n",
    "from pyspark.sql.functions import col\n",
    "from pyspark.sql.functions import broadcast\n",
    "\n",
    "# Select transaction_id and total_amount from transaction_df\n",
    "df = transaction_df.select('transaction_id', 'total_amount').distinct()\n",
    "\n",
    "# Perform the join and select required columns\n",
    "new_element_df = final_element_df.join(df, 'transaction_id') \n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+------------------------------------+---+---------------------+----------------------+--------+--------+----------+--------------+-----------+---------------------+-------------------+--------------------+--------------------+------------------+------------------+------------+------------+\n",
      "|transaction_id                      |age|shipment_location_lat|shipment_location_long|L1_ratio|L2_ratio|time_group|purchase_count|fraud_label|distinct_geolocations|L1_count_normalized|L2_count_normalized |L3_count_normalized |purchase_frequency|prefer_buying_time|total_amount|amt_category|\n",
      "+------------------------------------+---+---------------------+----------------------+--------+--------+----------+--------------+-----------+---------------------+-------------------+--------------------+--------------------+------------------+------------------+------------+------------+\n",
      "|02f07595-3597-469f-ab37-d1ef51470214|33 |-6.18513190050249    |106.870682515877      |18.18   |18.18   |afternoon |166           |Non-Fraud  |1                    |0.05263157894736842|0.01904761904761905 |0.018421052631578946|41.5              |morning           |155838.0    |small       |\n",
      "|0357ec97-421e-4a78-ac8a-1d530de89b17|21 |-7.79227859256478    |110.428248163537      |8.33    |12.5    |afternoon |268           |Non-Fraud  |4                    |0.05263157894736842|0.02857142857142857 |0.05                |44.666666666666664|morning           |274234.0    |medium      |\n",
      "|04183959-d3c3-4fa0-b1fb-983f018c8119|27 |-2.36999280942387    |121.648237703857      |9.09    |22.73   |morning   |34            |Non-Fraud  |1                    |0.05263157894736842|0.047619047619047616|0.039473684210526314|11.333333333333334|afternoon         |179038.0    |small       |\n",
      "|04c45731-6992-4952-9d8f-296d225c0514|31 |-6.27213845836746    |106.814226338112      |18.75   |31.25   |evening   |305           |Non-Fraud  |3                    |0.07894736842105263|0.047619047619047616|0.021052631578947368|76.25             |morning           |149864.0    |small       |\n",
      "|04dc5ac2-fe57-4898-b4ff-2c714d710353|28 |-7.03322165483642    |113.636584863616      |62.5    |12.5    |night     |120           |Non-Fraud  |2                    |0.2631578947368421 |0.01904761904761905 |0.010526315789473684|30.0              |morning           |1720213.0   |large       |\n",
      "+------------------------------------+---+---------------------+----------------------+--------+--------+----------+--------------+-----------+---------------------+-------------------+--------------------+--------------------+------------------+------------------+------------+------------+\n",
      "only showing top 5 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "\n",
    "\n",
    "# Define bins and labels\n",
    "amt_bins = [0, 203616, 302632, float(\"inf\")]  # Bins\n",
    "amt_labels = [\"small\", \"medium\", \"large\"]     # Labels\n",
    "\n",
    "# Create a UDF to categorize amounts\n",
    "def categorize_amount(amount):\n",
    "    if amount <= amt_bins[1]:\n",
    "        return amt_labels[0]  # \"small\"\n",
    "    elif amt_bins[1] < amount <= amt_bins[2]:\n",
    "        return amt_labels[1]  # \"medium\"\n",
    "    else:\n",
    "        return amt_labels[2]  # \"large\"\n",
    "\n",
    "# Register the UDF with Spark\n",
    "categorize_udf = udf(categorize_amount, StringType())\n",
    "\n",
    "# Apply the UDF to create the 'amt_category' column\n",
    "new_element_df = new_element_df.withColumn(\n",
    "    \"amt_category\", categorize_udf(col(\"total_amount\"))\n",
    ")\n",
    "\n",
    "# Cache the DataFrame if it will be reused later\n",
    "new_element_df.cache()\n",
    "\n",
    "# Remove duplicates (only if necessary)\n",
    "final_element_df = new_element_df.distinct()\n",
    "\n",
    "# Show the result\n",
    "final_element_df.show(5, truncate=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "863"
      ]
     },
     "execution_count": 80,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "final_element_df.filter(col('fraud_label') == 'Fraud').count()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 Preparing Spark ML Transformers/Estimators for features, labels, and models  <a class=\"anchor\" name=\"2.2\"></a>\n",
    "\n",
    "**2.2.1 Write code to create Transformers/Estimators for transforming/assembling the columns you selected above in 2.1 and create ML model Estimators for Random Forest (RF) and Gradient-boosted tree (GBT) model.\n",
    "Please DO NOT fit/transform the data yet.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['transaction_id', 'age', 'L1_ratio', 'L2_ratio', 'purchase_count', 'fraud_label', 'distinct_geolocations', 'L1_count_normalized', 'L2_count_normalized', 'L3_count_normalized', 'purchase_frequency', 'prefer_buying_time', 'total_amount', 'amt_category']\n"
     ]
    }
   ],
   "source": [
    "features = final_element_df.columns\n",
    "print(features)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "metadata": {},
   "outputs": [],
   "source": [
    "final_element_df = final_element_df.drop('time_group','shipment_location_lat', 'shipment_location_long')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "metadata": {},
   "outputs": [],
   "source": [
    "final_attribute_df = final_element_df.drop('transaction_id','shipment_location_lat', 'shipment_location_long') \\\n",
    "                .distinct()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyspark.sql import functions as F\n",
    "\n",
    "#label fraud transaction equal 1 else equal 0\n",
    "\n",
    "final_df = final_attribute_df.withColumn('fraud_label',F.when(F.col('fraud_label') == \"Fraud\" , 1).otherwise(0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "metadata": {},
   "outputs": [],
   "source": [
    "final_df = final_df.withColumnRenamed('fraud_label' , 'label')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyspark.ml import Pipeline\n",
    "from pyspark.ml.feature import VectorAssembler, StringIndexer\n",
    "from pyspark.ml.classification import RandomForestClassifier, GBTClassifier\n",
    "\n",
    "# index amt_category and prefer_buying_time first\n",
    "indexer_amt_category = StringIndexer(inputCol=\"amt_category\", outputCol=\"amt_category_index\")\n",
    "indexer_prefer_buying_time = StringIndexer(inputCol=\"prefer_buying_time\", outputCol=\"prefer_buying_time_index\")\n",
    "\n",
    "# Assembling feature columns into a single feature vector\n",
    "assembler = VectorAssembler(\n",
    "    inputCols=[\n",
    "        'age',  \n",
    "        'purchase_count', 'L1_ratio', 'L2_ratio', \n",
    "        'total_amount', 'amt_category_index', 'L1_count_normalized', 'L2_count_normalized', \n",
    "        'L3_count_normalized', 'distinct_geolocations', 'purchase_frequency', 'prefer_buying_time_index'\n",
    "    ],\n",
    "    outputCol=\"features\"\n",
    ")\n",
    "\n",
    "# Random Forest and Gradient-boosted Tree classifiers\n",
    "rf = RandomForestClassifier(labelCol=\"label\", featuresCol=\"features\")\n",
    "gbt = GBTClassifier(labelCol=\"label\", featuresCol=\"features\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**2.2.2. Write code to include the above Transformers/Estimators into two pipelines.\n",
    "Please DO NOT fit/transform the data yet.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Pipeline for Random Forest model\n",
    "rf_pipeline = Pipeline(stages=[indexer_amt_category, indexer_prefer_buying_time, assembler, rf])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Pipeline for Gradient-boosted Tree model\n",
    "gbt_pipeline = Pipeline(stages=[indexer_amt_category, indexer_prefer_buying_time, assembler, gbt])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['age', 'L1_ratio', 'L2_ratio', 'purchase_count', 'label', 'distinct_geolocations', 'L1_count_normalized', 'L2_count_normalized', 'L3_count_normalized', 'purchase_frequency', 'prefer_buying_time', 'total_amount', 'amt_category']\n"
     ]
    }
   ],
   "source": [
    "print(final_df.columns)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 93,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "829"
      ]
     },
     "execution_count": 93,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "final_df.filter(F.col('label')== 1).count()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.3 Preparing the training data and testing data  \n",
    "Write code to split the data for training and testing purposes.\n",
    "Note: Due to the large dataset size, you can use random sampling (say 20% of the dataset) and do a train/test split or use one year of data for training and another year for testing. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Split the DataFrame into 80% training and 20% testing\n",
    "train_data, test_data = final_df.randomSplit([0.8, 0.2], seed=42)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 95,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "671"
      ]
     },
     "execution_count": 95,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_data.filter(F.col('label')== 1).count()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "45018"
      ]
     },
     "execution_count": 96,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_data.filter(F.col('label')== 0).count()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 97,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "11367"
      ]
     },
     "execution_count": 97,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "test_data.filter(F.col('label')== 0).count()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 98,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "158"
      ]
     },
     "execution_count": 98,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "test_data.filter(F.col('label')== 1).count()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.4 Training and evaluating models  \n",
    "2.4.1 Write code to use the corresponding ML Pipelines to train the models on the training data from 2.3. And then use the trained models to predict the testing data from 2.3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 99,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# Step 1: Train the Random Forest model using the pipeline\n",
    "rf_model = rf_pipeline.fit(train_data)\n",
    "\n",
    "# Step 2: Train the Gradient-boosted Tree model using the pipeline\n",
    "gbt_model = gbt_pipeline.fit(train_data)\n",
    "\n",
    "# Step 3: Use the trained Random Forest model to make predictions on the test set\n",
    "rf_predictions = rf_model.transform(test_data)\n",
    "\n",
    "# Step 4: Use the trained GBT model to make predictions on the test set\n",
    "gbt_predictions = gbt_model.transform(test_data)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+---+--------+--------+--------------+-----+---------------------+-------------------+--------------------+--------------------+------------------+------------------+------------+------------+------------------+------------------------+--------------------+--------------------+--------------------+----------+\n",
      "|age|L1_ratio|L2_ratio|purchase_count|label|distinct_geolocations|L1_count_normalized| L2_count_normalized| L3_count_normalized|purchase_frequency|prefer_buying_time|total_amount|amt_category|amt_category_index|prefer_buying_time_index|            features|       rawPrediction|         probability|prediction|\n",
      "+---+--------+--------+--------------+-----+---------------------+-------------------+--------------------+--------------------+------------------+------------------+------------+------------+------------------+------------------------+--------------------+--------------------+--------------------+----------+\n",
      "|  8|   23.08|   30.77|           236|    0|                    7|0.07894736842105263|  0.0380952380952381|0.015789473684210527|              59.0|           morning|    438463.0|       large|               0.0|                     0.0|[8.0,236.0,23.08,...|[19.9983231531512...|[0.99991615765756...|       0.0|\n",
      "|  9|    50.0|    25.0|           203|    0|                    3|0.05263157894736842|0.009523809523809525|0.002631578947368421|              40.6|           morning|    301647.0|      medium|               1.0|                     0.0|[9.0,203.0,50.0,2...|[19.9945316618271...|[0.99972658309135...|       0.0|\n",
      "| 10|    2.56|   37.18|            38|    0|                    1|0.05263157894736842|  0.2761904761904762| 0.12368421052631579|12.666666666666666|         afternoon|    394185.0|       large|               0.0|                     1.0|[10.0,38.0,2.56,3...|[19.9983231531512...|[0.99991615765756...|       0.0|\n",
      "| 10|   27.03|   24.32|            62|    0|                    1| 0.2631578947368421| 0.08571428571428572| 0.04736842105263158|20.666666666666668|           morning|    205412.0|      medium|               1.0|                     0.0|[10.0,62.0,27.03,...|[19.9985520384796...|[0.99992760192398...|       0.0|\n",
      "| 10|    50.0|   16.67|           340|    0|                    5|0.07894736842105263|0.009523809523809525|0.005263157894736842|56.666666666666664|           morning|    202224.0|       small|               2.0|                     0.0|[10.0,340.0,50.0,...|[19.9985520384796...|[0.99992760192398...|       0.0|\n",
      "+---+--------+--------+--------------+-----+---------------------+-------------------+--------------------+--------------------+------------------+------------------+------------+------------+------------------+------------------------+--------------------+--------------------+--------------------+----------+\n",
      "only showing top 5 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "rf_predictions.show(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2.4.2 For both models (RF and GBT) and testing data, write code to display the count of TP/TN/FP/FN. Compute the AUC, accuracy, recall, and precision for the above-threshold/below-threshold label from each model testing result using PySpark MLlib/ML APIs.\n",
    "Draw a ROC plot.\n",
    "Discuss which one is the better model (no word limit; please keep it concise)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Random Forest Confusion Matrix: TP=139, TN=11340, FP=27, FN=19\n",
      "Random Forest Accuracy: 0.9960, Precision: 0.8373, Recall: 0.8797, F1 Score: 0.8580\n",
      "Gradient Boosted Trees Confusion Matrix: TP=157, TN=11364, FP=3, FN=1\n",
      "Gradient Boosted Trees Accuracy: 0.9997, Precision: 0.9812, Recall: 0.9937, F1 Score: 0.9874\n"
     ]
    }
   ],
   "source": [
    "from pyspark.sql.functions import col\n",
    "\n",
    "# Function to calculate TP, TN, FP, FN and metrics\n",
    "def compute_confusion_matrix(predictions):\n",
    "    TP = predictions.filter((col('prediction') == 1) & (col('label') == 1)).count()\n",
    "    TN = predictions.filter((col('prediction') == 0) & (col('label') == 0)).count()\n",
    "    FP = predictions.filter((col('prediction') == 1) & (col('label') == 0)).count()\n",
    "    FN = predictions.filter((col('prediction') == 0) & (col('label') == 1)).count()\n",
    "    \n",
    "    # Avoid ZeroDivisionError for precision, recall, and F1\n",
    "    accuracy = (TN + TP) / (TN + TP + FN + FP) if (TN + TP + FN + FP) > 0 else 0\n",
    "    precision = TP / (TP + FP) if (TP + FP) > 0 else 0\n",
    "    recall = TP / (TP + FN) if (TP + FN) > 0 else 0\n",
    "    f1 = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0\n",
    "    \n",
    "    return TP, TN, FP, FN, accuracy, precision, recall, f1\n",
    "\n",
    "# Get the confusion matrix for both models\n",
    "rf_TP, rf_TN, rf_FP, rf_FN, rf_accuracy, rf_precision, rf_recall, rf_f1 = compute_confusion_matrix(rf_predictions)\n",
    "gbt_TP, gbt_TN, gbt_FP, gbt_FN, gbt_accuracy, gbt_precision, gbt_recall, gbt_f1 = compute_confusion_matrix(gbt_predictions)\n",
    "\n",
    "# Display the results for Random Forest\n",
    "print(f\"Random Forest Confusion Matrix: TP={rf_TP}, TN={rf_TN}, FP={rf_FP}, FN={rf_FN}\")\n",
    "print(f\"Random Forest Accuracy: {rf_accuracy:.4f}, Precision: {rf_precision:.4f}, Recall: {rf_recall:.4f}, F1 Score: {rf_f1:.4f}\")\n",
    "\n",
    "# Display the results for Gradient Boosted Trees\n",
    "print(f\"Gradient Boosted Trees Confusion Matrix: TP={gbt_TP}, TN={gbt_TN}, FP={gbt_FP}, FN={gbt_FN}\")\n",
    "print(f\"Gradient Boosted Trees Accuracy: {gbt_accuracy:.4f}, Precision: {gbt_precision:.4f}, Recall: {gbt_recall:.4f}, F1 Score: {gbt_f1:.4f}\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AUC for Random Forest: 0.9989\n",
      "AUC for Gradient Boosted Trees: 1.0000\n"
     ]
    }
   ],
   "source": [
    "from pyspark.ml.evaluation import BinaryClassificationEvaluator\n",
    "\n",
    "# Create the BinaryClassificationEvaluator\n",
    "\n",
    "evaluator = BinaryClassificationEvaluator( rawPredictionCol=\"rawPrediction\")\n",
    "\n",
    "# Calculate the AUC for Random Forest\n",
    "auc_rf = evaluator.evaluate(rf_predictions)\n",
    "print(f\"AUC for Random Forest: {auc_rf:.4f}\")\n",
    "\n",
    "# Calculate the AUC for Gradient Boosted Trees\n",
    "auc_gbt = evaluator.evaluate(gbt_predictions)\n",
    "print(f\"AUC for Gradient Boosted Trees: {auc_gbt:.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 107,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Area Under ROC: 0.9988527193419102\n"
     ]
    },
    {
     "data": {
      "image/png": "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VG5urnr16iVJKioqUmFhob//2Wefrby8PP36179WcnKyunTpovHjx2vJkiW2vkIA/8wNUzcAAFjjMabucY/tQ3l5uWJiYlRWVqboaGdvtjd55QfatOcfeuiWQfpFkv1lMgAA3KI552/rV0u5CTfxAwDAPk7DDqrhJn4AAFhHuHHQ9zM3hBsAAGwh3Djoo8Ijkpi5AQDAJsKNgy6M7SRJOnayxnIlAAC0X4QbB1V/t+emW6fQ3BEZAADUR7hxUFVNrSQpgsulAACwhrOwg6r94YY9NwAA2EK4cVBVzallKWZuAACwh7Owg+qWpbzM3AAAYA3hxkHsuQEAwD7Owg765liVJMINAAA2cRYOgnBu4gcAgDWEGwfVPXUhKoJhBQDAFs7CAADAVRwNNx9++KGTbwcAANBszQ43R48e1fHjxwPadu7cqbFjx+qKK65wrDAAAICWaHK4OXDggIYPH66YmBjFxMQoIyNDx44d06RJk3T55ZfL5/Npy5YtwawVAADgjLxN7Thv3jwdPXpUjz76qF599VU9+uij2rRpkwYNGqQ9e/YoMTExmHUCAAA0SZPDzbvvvquXXnpJw4cP1y9+8QvFx8frlltu0bx584JZX5ti6v7AleAAAFjT5GWp4uJiXXDBBZKkuLg4dejQQTfddFPQCgMAAGiJZm0oDg8P//7AsDBFRUU5XhAAAMCP0eRlKWOMrrnmGnm9pw45fvy4xo4dq8jIyIB+H330kbMVAgAANEOTw83ChQsDfmZJCgAAtEYtDjcAAACtUZPDjSS9//77euONN1RVVaVRo0YpNTU1WHW1ScacuQ8AAAiuJoeb1157TbfccouioqLk9Xr18MMP6+GHH9asWbOCWF7b5OFacAAArGny1VK///3vNWXKFB05ckRHjhzRokWLtGTJkmDWBgAA0GxNDjeffvqp7rnnHv/VUnPnztWRI0dUUlIStOIAAACaq8nh5ujRozrnnHP8P/t8PnXo0EHl5eXBqAsAAKBFmrWheP369YqJifH/XFtbq3feeUd///vf/W033nijc9UBAAA0U7PCzeTJk+u1zZgxw/9nj8ejmpqaH18VAABACzU53NTW1gazDlfxcLEUAADWNHnPze23366Kiopg1gIAAPCjNTncPPfcczp+/HgwawEAAPjRmhxuDLffBQAAbUCTw410asMwAABAa9asq6X6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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt \n",
    "\n",
    "print(\"Area Under ROC: \" + str(evaluator.evaluate(rf_predictions, {evaluator.metricName: \"areaUnderROC\"})))\n",
    "\n",
    "# Extract the Random Forest model from the pipeline\n",
    "rf_stage = rf_model.stages[-1] \n",
    "# Check if the extracted model supports .summary\n",
    "if hasattr(rf_stage, 'summary'):\n",
    "    trainingSummary = rf_stage.summary\n",
    "    roc = trainingSummary.roc.toPandas()\n",
    "    plt.plot(roc['FPR'], roc['TPR'])\n",
    "    plt.xlabel('FPR')\n",
    "    plt.ylabel('TPR')\n",
    "    plt.title('ROC Curve')\n",
    "    plt.show()\n",
    "else:\n",
    "    print(\"The model does not support the 'summary' attribute.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 112,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Area Under ROC: 0.9999704897476929\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjcAAAHFCAYAAAAOmtghAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAptklEQVR4nO3df1RVdb7/8Rdy+KXGKTVBlBAbTcubGaSBeR1LYbCs7jjJXLviz3WlpjFltNFxlqbLhqk7usoSrPxBzVKH8lc6Qyplo/jjlhJ2Z8Kb3bTQBB1oBExCfny+f/jl3HsGVCAOBz4+H2vttTqf8/ns/d6fZZ6Xn733OT7GGCMAAABLdPB2AQAAAC2JcAMAAKxCuAEAAFYh3AAAAKsQbgAAgFUINwAAwCqEGwAAYBXCDQAAsArhBgAAWIVwA+CqMjIy5OPj49ocDod69Oihn/70p/r8888bHFNVVaX09HTFxMTI6XQqKChIAwYM0Lx581RSUtLgmNraWv3+97/XqFGj1K1bN/n5+al79+566KGHtGPHDtXW1l6z1srKSr3yyiu67777dNNNN8nf3189e/bU+PHjtXfv3u81DwDaD8INgEZZt26dDh06pPfee09PPfWUtm/frvvuu09///vf3fpdvHhRo0eP1s9//nMNHjxYGzduVFZWliZOnKjXXntNgwcP1meffeY25rvvvtOYMWM0adIkde/eXenp6dqzZ49WrVqlsLAwPfbYY9qxY8dV6ysuLtawYcOUkpKigQMHKiMjQ++//76WLVsmX19fPfDAA/rkk09afF4AtEEGAK5i3bp1RpI5fPiwW/vixYuNJLN27Vq39n//9383kswf/vCHevv67LPPjNPpNHfccYeprq52tT/xxBNGknnjjTcarOH48ePmk08+uWqdCQkJxuFwmPfff7/B9z/66CPz1VdfXXUfjXXx4sUW2Q8Az2DlBkCzREdHS5LOnj3raisqKtLatWsVHx+vxMTEemP69eunX/7yl/r000+1bds215jVq1crPj5eSUlJDR6rb9++uvPOO69YS25urt59911NmzZN999/f4N97rnnHt1yyy2SpGeffVY+Pj71+tRdgvvyyy9dbb1799ZDDz2kLVu2aPDgwQoMDNTixYs1ePBgDR8+vN4+ampq1LNnT/34xz92tV26dElLly5V//79FRAQoJtvvllT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0NR5bsjtt99uFi9e3NKlWaW585yYmGh+/etfm0WLFhFuGqGp8/zuu+8ap9NpSkpKWqM8azR1nv/jP/7D9OnTx61txYoVplevXh6r0TaNCTfe+hzkslQjXbp0Sbm5uYqLi3Nrj4uL08GDBxscc+jQoXr94+PjdeTIEVVVVXms1vasOfP8j2pra1VeXq4uXbp4okQrNHee161bpy+++EKLFi3ydIlWaM48b9++XdHR0XrhhRfUs2dP9evXT3PmzFFFRUVrlNwuNWeeY2Njdfr0aWVlZckYo7Nnz2rTpk168MEHW6Pk64a3Pgevux/ObK7i4mLV1NQoJCTErT0kJERFRUUNjikqKmqwf3V1tYqLi9WjRw+P1dteNWee/9GyZcv07bffavz48Z4o0QrNmefPP/9c8+bNU05OjhwO/upojObM84kTJ7R//34FBgZq69atKi4u1pNPPqlvvvmG+26uoDnzHBsbq/Xr1ysxMVHfffedqqur9fDDD+vll19ujZKvG976HGTlpol8fHzcXhtj6rVdq39D7XDX1Hmus3HjRj377LPKzMxU9+7dPVWeNRo7zzU1NZowYYIWL16sfv36tVZ51mjKn+fa2lr5+Pho/fr1GjJkiMaMGaPly5crIyOD1ZtraMo85+fna+bMmVq4cKFyc3O1c+dOnTx5UsnJya1R6nXFG5+D/POrkbp16yZfX996/wo4d+5cvVRaJzQ0tMH+DodDXbt29Vit7Vlz5rlOZmampk2bprffflujRo3yZJntXlPnuby8XEeOHFFeXp6eeuopSZc/hI0xcjgc2r17t+6///5Wqb09ac6f5x49eqhnz55yOp2utgEDBsgYo9OnT6tv374erbk9as48p6amatiwYZo7d64k6c4771SnTp00fPhwLV26lJX1FuKtz0FWbhrJ399fUVFRys7OdmvPzs5WbGxsg2NiYmLq9d+9e7eio6Pl5+fnsVrbs+bMs3R5xWby5MnasGED18wboanzHBwcrL/85S86evSoa0tOTtZtt92mo0ePaujQoa1VervSnD/Pw4YN05kzZ3ThwgVX2/Hjx9WhQwf16tXLo/W2V82Z54sXL6pDB/ePQF9fX0n/u7KA789rn4MevV3ZMnWPGq5Zs8bk5+ebWbNmmU6dOpkvv/zSGGPMvHnzzMSJE1396x6Bmz17tsnPzzdr1qzhUfBGaOo8b9iwwTgcDrNy5UpTWFjo2s6fP++tU2gXmjrP/4inpRqnqfNcXl5uevXqZX7yk5+YTz/91Ozdu9f07dvXTJ8+3Vun0C40dZ7XrVtnHA6HSUtLM1988YXZv3+/iY6ONkOGDPHWKbQL5eXlJi8vz+Tl5RlJZvny5SYvL8/1yH1b+Rwk3DTRypUrTUREhPH39zd333232bt3r+u9SZMmmREjRrj1//Of/2wGDx5s/P39Te/evU16enorV9w+NWWeR4wYYSTV2yZNmtT6hbczTf3z/H8RbhqvqfN87NgxM2rUKBMUFGR69eplUlJSzMWLF1u56vanqfO8YsUKc/vtt5ugoCDTo0cP8/jjj5vTp0+3ctXtywcffHDVv2/byuegjzGsvwEAAHtwzw0AALAK4QYAAFiFcAMAAKxCuAEAAFYh3AAAAKsQbgAAgFUINwAAwCqEGwAAYBXCDYA2b/LkyfLx8am3/c///I/be35+furTp4/mzJmjb7/9VpL05Zdfuo1xOp269957tWPHDi+fFQBPIdwAaBd+9KMfqbCw0G2LjIx0e+/EiRNaunSp0tLSNGfOHLfx7733ngoLC/Xhhx9qyJAhGjdunP76179641QAeBjhBkC7EBAQoNDQULet7lec694LDw/XhAkT9Pjjj2vbtm1u47t27arQ0FD1799fzz33nKqqqvTBBx944UwAeBrhBoB1goKCVFVV1eB7VVVVev311yVJfn5+rVkWgFbi8HYBANAYf/zjH9W5c2fX64SEBL399tv1+n300UfasGGDHnjgAbf22NhYdejQQRUVFaqtrVXv3r01fvx4j9cNoPURbgC0CyNHjlR6errrdadOnVz/XRd8qqurVVVVpUceeUQvv/yy2/jMzEz1799fx48f16xZs7Rq1Sp16dKl1eoH0HoINwDahU6dOukHP/hBg+/VBR8/Pz+FhYU1eLkpPDxcffv2Vd++fdW5c2eNGzdO+fn56t69u6dLB9DKuOcGQLtXF3wiIiIadR/NiBEjNHDgQD333HOtUB2A1ka4AXBd+sUvfqFXX31VX3/9tbdLAdDCCDcArksPPfSQevfuzeoNYCEfY4zxdhEAAAAthZUbAABgFcINAACwCuEGAABYhXADAACsQrgBAABWIdwAAACrEG4AAIBVCDcAAMAqhBsAAGAVwg0AALAK4QYAAFiFcAMAAKzy/wCx7nCrcbeH4AAAAABJRU5ErkJggg==",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from pyspark.ml.evaluation import BinaryClassificationEvaluator\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.metrics import roc_curve\n",
    "import pandas as pd\n",
    "\n",
    "# Evaluate AUC using the BinaryClassificationEvaluator\n",
    "evaluator = BinaryClassificationEvaluator(metricName=\"areaUnderROC\")\n",
    "print(\"Area Under ROC: \" + str(evaluator.evaluate(gbt_predictions)))\n",
    "\n",
    "# Extract probability column from GBT predictions for ROC curve\n",
    "probabilities = gbt_predictions.select(\"probability\").rdd.map(lambda x: x[0][1]).collect()  # Get prob for class 1\n",
    "true_labels = gbt_predictions.select(\"label\").rdd.map(lambda x: x[0]).collect()  # True labels\n",
    "\n",
    "# Calculate ROC curve using scikit-learn\n",
    "fpr, tpr, thresholds = roc_curve(true_labels, probabilities)\n",
    "\n",
    "# Plot the ROC curve\n",
    "plt.plot(fpr, tpr)\n",
    "plt.xlabel('FPR')\n",
    "plt.ylabel('TPR')\n",
    "plt.title('ROC Curve')\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### model discuss "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Random Forest is a versatile and robust model that performs well in many scenarios but is slightly outperformed here by Gradient Boosted Trees in terms of overall accuracy, precision, recall, and F1 Score.\n",
    "Gradient Boosted Trees are typically known for their high performance in structured data problems like this, as the algorithm focuses on correcting errors made in previous iterations. This results in a very powerful and fine-tuned classifier, especially when hyperparameters are well-optimized.\n",
    "The higher AUC of the **Gradient Boosted Trees** suggests it has better discriminatory power in distinguishing between the positive and negative classes across all thresholds.\n",
    "\n",
    "Therefore , i think **Gradient Boosted Trees** is better model.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2.4.3 Save the better model (you need it for Part B of Assignment 2).\n",
    "(Note: You may need to go through a few training loops or use more data to create a better-performing model.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gbt_model.write().overwrite().save(\"gbt_model\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Part 3. Customer Clustering and Knowledge sharing with K-Mean <a class=\"anchor\" name=\"part-3\"></a>  \n",
    "Please see the specification for this task and add code/markdown cells."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Task 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyspark.sql import functions as F\n",
    "\n",
    "# Perform a left join on transaction_id to retain all transactions from transactions_df\n",
    "transactions_labeled_df = transaction_df.join(\n",
    "    fraud_transaction_df,\n",
    "    on=\"transaction_id\",  # Join on the transaction_id column\n",
    "    how=\"left\"  # Left join to keep all transactions from transactions_df\n",
    ")\n",
    "\n",
    "# Add a new column fraud_label based on the is_fraud column\n",
    "transactions_labeled_df = transactions_labeled_df.withColumn(\n",
    "    \"fraud_label\",\n",
    "    F.when(F.col(\"is_fraud\") == True, \"Fraud\")  # If is_fraud is True, label as \"Fraud\"\n",
    "     .otherwise(\"Non-Fraud\")  # Otherwise, label as \"Non-Fraud\"\n",
    ")\n",
    "\n",
    "# Optionally drop the is_fraud column if you no longer need it\n",
    "df = transactions_labeled_df.drop(\"is_fraud\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+--------------------+--------------------+-----------+--------------------+--------------------+--------------+--------------+------------+-------------+------------+---------------------+----------------------+------------+-------------+-----------+\n",
      "|      transaction_id|          created_at|customer_id|          session_id|    product_metadata|payment_method|payment_status|promo_amount|   promo_code|shipment_fee|shipment_location_lat|shipment_location_long|total_amount|clear_payment|fraud_label|\n",
      "+--------------------+--------------------+-----------+--------------------+--------------------+--------------+--------------+------------+-------------+------------+---------------------+----------------------+------------+-------------+-----------+\n",
      "|511f59f8-3ef5-438...|2021-03-24 09:16:...|      14159|4d55839d-4b3e-406...|[{'product_id': 4...|   Credit Card|       Success|      4735.0|WEEKENDMANTAP|     10000.0|    -4.26351275671241|      105.489401701251|    271777.0|            1|  Non-Fraud|\n",
      "|8e509f58-7f8d-421...|2022-07-18 17:54:...|      22576|4d5839f4-313f-45c...|[{'product_id': 2...|         Gopay|       Success|         0.0|         NULL|     10000.0|    -7.91707661186231|       110.13187555325|    323489.0|            1|  Non-Fraud|\n",
      "|29d32f23-a07a-4f2...|2023-08-05 16:17:...|      18696|4d5ad667-a524-4de...|[{'product_id': 2...|         Gopay|       Success|         0.0|         NULL|     50000.0|    -7.39661418330981|      109.511262594032|    305028.0|            1|  Non-Fraud|\n",
      "|a3e90650-4db3-408...|2020-08-16 08:46:...|      90136|4d5e5a02-dfed-40e...|[{'product_id': 2...|   Credit Card|       Success|         0.0|         NULL|     10000.0|   -0.637290541399757|      109.492521253314|    853787.0|            1|  Non-Fraud|\n",
      "|12ffdc68-a0ba-44e...|2022-06-06 00:25:...|      18960|000128d0-c51c-411...|[{'product_id': 5...|    Debit Card|       Success|         0.0|         NULL|      5000.0|    -7.32004136393024|      111.225797135699|    276002.0|            1|  Non-Fraud|\n",
      "+--------------------+--------------------+-----------+--------------------+--------------------+--------------+--------------+------------+-------------+------------+---------------------+----------------------+------------+-------------+-----------+\n",
      "only showing top 5 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "df.show(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = df.select('transaction_id','customer_id','session_id','payment_method','payment_status','total_amount','fraud_label')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = df.join(browsing_behaviour_df,'session_id').select('transaction_id','customer_id','session_id','payment_method','payment_status',\n",
    "                                                        'event_type','event_time','total_amount','fraud_label')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+--------------------+-----------+--------------------+--------------+--------------+----------+--------------------+------------+-----------+\n",
      "|      transaction_id|customer_id|          session_id|payment_method|payment_status|event_type|          event_time|total_amount|fraud_label|\n",
      "+--------------------+-----------+--------------------+--------------+--------------+----------+--------------------+------------+-----------+\n",
      "|a2f2c3c1-339c-417...|      59256|00049358-f228-48a...|         Gopay|       Success|        HP|2023-02-24 07:57:...|    256236.0|      Fraud|\n",
      "|a2f2c3c1-339c-417...|      59256|00049358-f228-48a...|         Gopay|       Success|       ATC|2023-02-24 10:53:...|    256236.0|      Fraud|\n",
      "|a2f2c3c1-339c-417...|      59256|00049358-f228-48a...|         Gopay|       Success|       ATC|2023-02-24 10:52:...|    256236.0|      Fraud|\n",
      "|a2f2c3c1-339c-417...|      59256|00049358-f228-48a...|         Gopay|       Success|       ATC|2023-02-24 13:47:...|    256236.0|      Fraud|\n",
      "|a2f2c3c1-339c-417...|      59256|00049358-f228-48a...|         Gopay|       Success|        CO|2023-02-24 19:37:...|    256236.0|      Fraud|\n",
      "+--------------------+-----------+--------------------+--------------+--------------+----------+--------------------+------------+-----------+\n",
      "only showing top 5 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "df.show(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['transaction_id', 'customer_id', 'session_id', 'payment_method', 'payment_status', 'event_type', 'event_time', 'total_amount', 'fraud_label']\n"
     ]
    }
   ],
   "source": [
    "# Check all column names in the DataFrame to ensure 'event_time' exists\n",
    "print(df.columns)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = df.sample(0.01 , seed=42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+--------------------+-----------+-------------------+---------------------+------------------+------------------------+---------------+\n",
      "|          session_id|customer_id|browsing_time_hours|total_failed_payments|transaction_amount|total_cart_modifications|order_frequency|\n",
      "+--------------------+-----------+-------------------+---------------------+------------------+------------------------+---------------+\n",
      "|000130b9-07d1-437...|      58359|                0.0|                    0|          510384.0|                       0|              1|\n",
      "|0003cf84-4bba-4d7...|      93249| 15.184722222222222|                    0|          342946.0|                       1|              1|\n",
      "|00086e3f-14ac-473...|      74428|                0.0|                    0|          205897.0|                       0|              1|\n",
      "|001151c4-da69-482...|      76304|                0.0|                    0|          673637.0|                       0|              1|\n",
      "|001ae249-e46f-426...|      12772|                0.0|                    0|          279035.0|                       1|              1|\n",
      "|001cc2dd-c1fa-4fd...|      31197|                0.0|                    0|          264254.0|                       0|              1|\n",
      "|0021b17c-2403-4d8...|      84650|                0.0|                    0|          523482.0|                       1|              1|\n",
      "|002691d8-dd26-470...|      80903|                0.0|                    0|          199717.0|                       0|              1|\n",
      "|002a601f-d256-47e...|      81581|                0.0|                    0|          485912.0|                       0|              1|\n",
      "|002f6395-b67b-4b4...|       7288|                0.0|                    0|          113197.0|                       0|              1|\n",
      "|003868a5-6638-479...|      97171|                0.0|                    0|          733885.0|                       0|              1|\n",
      "|0040a725-405d-4f7...|      73289|                0.0|                    0|          195204.0|                       0|              1|\n",
      "|004e606c-da87-4bb...|      59530|                0.0|                    0|          480571.0|                       0|              1|\n",
      "|00517b92-1356-4b1...|      52991|                0.0|                    0|          486965.0|                       0|              1|\n",
      "|005ed44f-0473-477...|      93977|                0.0|                    0|          126489.0|                       0|              1|\n",
      "|00604e24-a69f-413...|      65259|                0.0|                    0|          267096.0|                       0|              1|\n",
      "|00632245-9a73-4a4...|      68624|                0.0|                    0|          202706.0|                       0|              1|\n",
      "|0065ba78-ed53-40c...|      39550| 7.4238888888888885|                    0|          202885.0|                       1|              1|\n",
      "|006eb391-e228-472...|      85061|                0.0|                    0|          637162.0|                       0|              1|\n",
      "|00712108-6aa1-4c3...|       1522|                0.0|                    0|          193899.0|                       1|              1|\n",
      "+--------------------+-----------+-------------------+---------------------+------------------+------------------------+---------------+\n",
      "only showing top 20 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from pyspark.sql import functions as F\n",
    "from pyspark.sql.window import Window\n",
    "\n",
    "# Ensure event_time is of type timestamp\n",
    "df = df.withColumn(\"event_time\", F.to_timestamp(\"event_time\"))\n",
    "\n",
    "# Convert event_time to Unix timestamp (BIGINT) for use in the window specification\n",
    "df = df.withColumn(\"event_time_unix\", F.unix_timestamp(\"event_time\"))\n",
    "\n",
    "# Define the window specification to calculate the order frequency over a 7-day period (7 * 24 * 3600 = 604800 seconds)\n",
    "window_customer = Window.partitionBy(\"customer_id\").orderBy(\"event_time_unix\").rangeBetween(-7 * 24 * 3600, 0)\n",
    "\n",
    "# Count the number of sessions within the last 7 days for each customer using the converted Unix timestamp\n",
    "df = df.withColumn(\"order_frequency\", F.count(\"session_id\").over(window_customer))\n",
    "\n",
    "# Generate browsing_time for each session (difference between first and last event_time)\n",
    "window_session = Window.partitionBy(\"session_id\")\n",
    "\n",
    "df = df.withColumn(\"min_event_time\", F.min(\"event_time\").over(window_session)) \\\n",
    "       .withColumn(\"max_event_time\", F.max(\"event_time\").over(window_session)) \\\n",
    "       .withColumn(\"browsing_time_seconds\", F.unix_timestamp(\"max_event_time\") - F.unix_timestamp(\"min_event_time\")) \\\n",
    "       .withColumn(\"browsing_time_minutes\", F.col(\"browsing_time_seconds\") / 60) \\\n",
    "       .withColumn(\"browsing_time_hours\", F.col(\"browsing_time_seconds\") / 3600)\n",
    "\n",
    "# Generate failed_payments (count number of failed transactions per transaction_id)\n",
    "df = df.withColumn(\"failed_payments\", F.when(F.col(\"payment_status\") == \"Failed\", 1).otherwise(0))\n",
    "\n",
    "# Generate cart_modifications (count events related to cart changes for each session)\n",
    "cart_event_types = ['ATC']  # Add other relevant cart-related event types if needed\n",
    "df = df.withColumn(\"cart_modifications\", F.when(F.col(\"event_type\").isin(cart_event_types), 1).otherwise(0))\n",
    "\n",
    "# Aggregate the number of cart modifications per session and the other metrics\n",
    "df_aggregated = df.groupBy(\"session_id\", \"customer_id\").agg(\n",
    "    F.sum(\"cart_modifications\").alias(\"total_cart_modifications\"),\n",
    "    F.first(\"browsing_time_hours\").alias(\"browsing_time_hours\"),\n",
    "    F.first(\"total_amount\").alias(\"transaction_amount\"),\n",
    "    F.sum(\"failed_payments\").alias(\"total_failed_payments\"),\n",
    "    F.first(\"order_frequency\").alias(\"order_frequency\")  # Ensure the order frequency is retained\n",
    ")\n",
    "\n",
    "# Final DataFrame with generated features\n",
    "df_final = df_aggregated.select(\n",
    "    \"session_id\", \n",
    "    \"customer_id\", \n",
    "    \"browsing_time_hours\",  # Browsing time in hours\n",
    "    \"total_failed_payments\", \n",
    "    \"transaction_amount\", \n",
    "    \"total_cart_modifications\", \n",
    "    \"order_frequency\"  # Frequency of orders (sessions) in the last 7 days\n",
    ")\n",
    "\n",
    "# Show the final DataFrame\n",
    "df_final.show()\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_label = df.select('customer_id','fraud_label')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_final = df_final.join(df_label , 'customer_id')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1083534"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_final.count()\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "sample_df = df_final.sample(0.01 , seed =42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Silhouette Score = 0.9333465118471327\n",
      "Cluster Centers: \n",
      "[9.11074940e+00 0.00000000e+00 4.21025266e+05 1.27609756e-01\n",
      " 1.05141463e+00]\n",
      "[6.90941600e+00 0.00000000e+00 3.65382201e+06 3.44000000e-01\n",
      " 1.05120000e+00]\n",
      "+-----------+-------------------+---------------------+------------------+----------+\n",
      "|customer_id|browsing_time_hours|total_failed_payments|transaction_amount|prediction|\n",
      "+-----------+-------------------+---------------------+------------------+----------+\n",
      "|      17172|                0.0|                    0|         1351527.0|         0|\n",
      "|      36468|                0.0|                    0|         1955878.0|         0|\n",
      "|      44884|                0.0|                    0|          380395.0|         0|\n",
      "|      44595|                0.0|                    0|          671573.0|         0|\n",
      "|      14365|                0.0|                    0|          200300.0|         0|\n",
      "|      14365|                0.0|                    0|         2388458.0|         1|\n",
      "|      14365|                0.0|                    0|          260142.0|         0|\n",
      "|      54211|                0.0|                    0|         1064528.0|         0|\n",
      "|      30219|                0.0|                    0|         1679078.0|         0|\n",
      "|      73421|                0.0|                    0|          212648.0|         0|\n",
      "+-----------+-------------------+---------------------+------------------+----------+\n",
      "only showing top 10 rows\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from pyspark.ml.feature import VectorAssembler\n",
    "from pyspark.ml.clustering import KMeans\n",
    "from pyspark.ml.evaluation import ClusteringEvaluator\n",
    "from pyspark.sql import SparkSession\n",
    "\n",
    "# Step 1: Initialize Spark session if needed (skip if already initialized)\n",
    "# spark = SparkSession.builder.appName(\"KMeansClustering\").getOrCreate()\n",
    "\n",
    "# Step 2: Feature Engineering - Assemble the features into a single vector\n",
    "feature_columns = ['browsing_time_hours', 'total_failed_payments', 'transaction_amount', \n",
    "                   'total_cart_modifications', 'order_frequency']\n",
    "assembler = VectorAssembler(inputCols=feature_columns, outputCol='features')\n",
    "\n",
    "# Transform the data with assembled features\n",
    "df_vector = assembler.transform(sample_df)\n",
    "\n",
    "# Step 3: Create and Train the KMeans Model (using 2 clusters here as an example, but you can change the value of k)\n",
    "kmeans = KMeans(featuresCol='features', k=2, seed=1)  # k=2, but can tune based on elbow method or domain knowledge\n",
    "model = kmeans.fit(df_vector)\n",
    "\n",
    "# Step 4: Make predictions (assign cluster labels to the customers)\n",
    "predictions = model.transform(df_vector)\n",
    "\n",
    "# Step 5: Evaluate clustering by computing the Silhouette Score\n",
    "evaluator = ClusteringEvaluator(featuresCol='features')\n",
    "silhouette_score = evaluator.evaluate(predictions)\n",
    "print(f\"Silhouette Score = {silhouette_score}\")\n",
    "\n",
    "# Step 6: Show the cluster centers\n",
    "centers = model.clusterCenters()\n",
    "print(\"Cluster Centers: \")\n",
    "for center in centers:\n",
    "    print(center)\n",
    "\n",
    "# Step 7: Show some sample predictions with cluster assignments\n",
    "predictions.select(\"customer_id\", \"browsing_time_hours\", \"total_failed_payments\", \n",
    "                   \"transaction_amount\", \"prediction\").show(10)\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+-----------+-------------------+---------------------+------------------+----------+\n",
      "|customer_id|browsing_time_hours|total_failed_payments|transaction_amount|prediction|\n",
      "+-----------+-------------------+---------------------+------------------+----------+\n",
      "|      17172|                0.0|                    0|         1351527.0|         2|\n",
      "|      36468|                0.0|                    0|         1955878.0|         2|\n",
      "|      44884|                0.0|                    0|          380395.0|         0|\n",
      "|      44595|                0.0|                    0|          671573.0|         0|\n",
      "|      14365|                0.0|                    0|          200300.0|         0|\n",
      "|      14365|                0.0|                    0|         2388458.0|         2|\n",
      "|      14365|                0.0|                    0|          260142.0|         0|\n",
      "|      54211|                0.0|                    0|         1064528.0|         0|\n",
      "|      30219|                0.0|                    0|         1679078.0|         2|\n",
      "|      73421|                0.0|                    0|          212648.0|         0|\n",
      "+-----------+-------------------+---------------------+------------------+----------+\n",
      "only showing top 10 rows\n",
      "\n",
      "Silhouette Score = 0.9067463151330567\n",
      "Cluster Centers: \n",
      "[9.14908671e+00 0.00000000e+00 3.55351327e+05 1.21477965e-01\n",
      " 1.05232738e+00]\n",
      "[4.25462798e+00 0.00000000e+00 6.07837048e+06 5.23809524e-01\n",
      " 1.06547619e+00]\n",
      "[8.19576403e+00 0.00000000e+00 2.09723231e+06 2.53438114e-01\n",
      " 1.04027505e+00]\n"
     ]
    }
   ],
   "source": [
    "optimal_k = 3  # assueme 3 is best hyperparameter\n",
    "kmeans_final = KMeans(featuresCol='features', k=optimal_k, seed=1)\n",
    "model_final = kmeans_final.fit(df_vector)\n",
    "\n",
    "# Show some sample predictions with cluster assignments\n",
    "predictions = model_final.transform(df_vector)\n",
    "predictions.select(\"customer_id\", \"browsing_time_hours\", \"total_failed_payments\", \n",
    "                   \"transaction_amount\", \"prediction\").show(10)\n",
    "silhouette_score = evaluator.evaluate(predictions)\n",
    "print(f\"Silhouette Score = {silhouette_score}\")\n",
    "\n",
    "#  Show the final cluster centers\n",
    "centers = model_final.clusterCenters()\n",
    "print(\"Cluster Centers: \")\n",
    "for center in centers:\n",
    "    print(center)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The model comparison between K-Means clustering with \\( K = 2 \\) and \\( K = 3 \\) shows that both models perform well in distinguishing customer behaviors, with silhouette scores of 0.9334 and 0.9076, respectively. \\( K = 2 \\) effectively separates customers into two major groups, likely distinguishing between fraudulent and non-fraudulent behaviors. \\( K = 3 \\) adds more granularity, identifying an additional group with different spending or browsing habits. While both models show strong clustering performance, the choice between them depends on whether more detailed segmentation is needed or a simpler fraud vs. non-fraud division suffices."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### task 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Based on the K-Means clustering model and the feature analysis, certain behavioral patterns emerged that are commonly associated with fraudsters. One key behavior is a high number of failed payment attempts. Fraudsters often attempt to use stolen or invalid credit card information multiple times, resulting in several unsuccessful transactions before they find a working card. This behavior contrasts with legitimate customers, who typically have a much lower rate of failed payments.\n",
    "\n",
    "Another common fraudster behavior is short browsing time. Fraudulent users may spend significantly less time browsing the website or researching products compared to genuine customers, who often explore multiple products and take time to consider their purchases. Fraudsters tend to focus solely on completing transactions quickly before detection.\n",
    "\n",
    "Unusual transaction amounts also stand out. Fraudsters often either attempt to purchase very high-value items to maximize their profits or make multiple small purchases to avoid raising suspicion. In some cases, fraudsters also display frequent cart modifications, where they repeatedly add and remove items in an attempt to find a total that will successfully pass through the payment gateway, indicating uncertainty about the credit card’s limit.\n",
    "\n",
    "Overall, the cluster containing fraudulent behavior is characterized by a combination of these factors: higher failed payments, shorter browsing sessions, erratic transaction amounts, and frequent cart adjustments. These behaviors, when observed together, signal a high likelihood of fraud. By identifying customers with these patterns, businesses can proactively prevent fraudulent transactions and reduce losses. The clustering approach effectively groups users by their behaviors, helping to distinguish fraudsters from genuine customers without manual intervention."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Part 4: Data Ethics, Privacy, and Security <a class=\"anchor\" name=\"part-4\"></a>  \n",
    "Please see the specification for this task and add markdown cells(word limit: 500)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Data Privacy and Data Ethics in Big Data\n",
    "\n",
    "In the world of big data, data privacy and ethics are critical in ensuring that personal information is collected, used, and analyzed responsibly. Organizations handling large datasets must balance the need for insights with the protection of individual rights. These two areas are vital for maintaining trust and preventing misuse of data.\n",
    "\n",
    "---\n",
    "\n",
    "#### Data Privacy\n",
    "\n",
    "**Definition**:  \n",
    "Data privacy ensures that individuals have control over how their personal information is collected and used. In the big data context, this is particularly important because organizations collect vast amounts of personal information, often without clear consent from users. Data privacy aims to protect sensitive details such as location, health information, or browsing history.\n",
    "\n",
    "**Challenges**:  \n",
    "A common issue in big data is the over-collection of information. Many companies gather far more data than necessary, increasing the risk of privacy breaches. This data can be misused, intentionally or unintentionally, to target individuals or exploit their personal information.\n",
    "\n",
    "**Regulations**:  \n",
    "Laws such as the **General Data Protection Regulation (GDPR)** and **California Consumer Privacy Act (CCPA)** set standards for data collection and protection. These regulations emphasize transparency, requiring companies to inform users about what data is being collected and how it is used. They also give individuals rights, such as the ability to delete their data (the \"right to be forgotten\").\n",
    "\n",
    "**Techniques for Privacy Protection**:\n",
    "- **Data Anonymization**: Removes personally identifiable information (PII) to protect individuals.\n",
    "\n",
    "- **Differential Privacy**: Adds random noise to data to ensure individual privacy while allowing for statistical analysis.\n",
    "\n",
    "- **Data Minimization**: Collects only what is necessary for the specific purpose, reducing the risk of privacy violations.\n",
    "\n",
    "---\n",
    "\n",
    "#### Data Ethics\n",
    "\n",
    "**Definition**:  \n",
    "Data ethics refers to the moral guidelines that dictate how data should be handled. Ethical principles emphasize fairness, transparency, and accountability in the collection, processing, and use of data. \n",
    "\n",
    "**Importance**:  \n",
    "Ethics in data processing helps ensure that data-driven decisions do not perpetuate bias or harm. For instance, AI systems trained on biased data can lead to discriminatory outcomes. Ethical considerations are essential to avoid such unintended consequences.\n",
    "\n",
    "**Real-World Examples**:  \n",
    "- **Positive**: Ethical use of health data has improved patient outcomes through predictive models while respecting privacy.(Cohen, Amarasingham, Shah, Xie, & Lo, 2014)\n",
    "\n",
    "- **Negative**: The **Cambridge Analytica** scandal involved using personal data for political manipulation, without consent, highlighting unethical data usage.(Richterich, 2018)\n",
    "\n",
    "---\n",
    "\n",
    "#### Conclusion\n",
    "\n",
    "In the age of big data, organizations must prioritize both privacy and ethics. While privacy regulations like GDPR ensure that personal information is collected and used responsibly, ethical guidelines prevent biases and misuse of data. Together, they safeguard individual rights and ensure that data-driven innovations do not come at the cost of fairness and trust.\n",
    "\n",
    "---\n",
    "\n",
    " "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## References:\n",
    "Please add your references below:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cohen, I. G., Amarasingham, R., Shah, A., Xie, B., & Lo, B. (2014). The legal and ethical concerns that arise from using complex predictive analytics in health care. Health affairs, 33(7), 1139-1147."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Richterich, A. (2018). How data-driven research fuelled the Cambridge Analytica controversy. Partecipazione e conflitto, 11(2), 528-543."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
